Custom Context Propagation in Spring Web flux with ThreadLocalAccessor, ContextRegistry, MDC, and GraphQL Interceptors

Abhishek Anand
Javarevisited
Published in
5 min readMar 2, 2024
Photo by Jace & Afsoon on Unsplash

In the intricate world of micro services, seamlessly transporting crucial context information among disparate components is essential. This information serves as a vital digital thread, guaranteeing consistency, traceability, and efficient debugging across the entire system. This blog delves into the core concepts of ThreadLocalAccessor, ContextRegistry, Mapped Diagnostic Context (MDC), and Interceptors within the context of Spring Web flux applications. We’ll explore their roles in context propagation and delve into the rationale behind logging context information.

Understanding ThreadLocalAccessor, ContextRegistry, and MDC

ThreadLocalAccessor: This utility facilitates the association of values with individual threads, ensuring thread safety and enabling access to thread specific data from any part of the code execution thread. It’s particularly valuable in asynchronous programming paradigms like Spring Web flux, where context needs to be preserved across asynchronous operations.

ContextRegistry: This component functions as a central hub for managing and accessing context data throughout an application’s execution. It offers methods for registering, retrieving, and unregistering context objects, streamlining context propagation across diverse application layers. Think of it as a central repository where all context information resides. Services can readily deposit and retrieve context data, ensuring consistent access across the application.

MDC (Mapped Diagnostic Context): This logging framework extension provides a mechanism for associating key-value pairs with the current thread’s context, allowing logging statements to incorporate this contextual information. This enrichment enhances log readability, troubleshooting, and correlation. Imagine a log statement indicating an error during order processing. By including a correlation ID (stored in MDC) within the log, developers can effortlessly trace the specific order encountering the issue and expedite troubleshooting efforts.

The Role of Interceptors: Orchestrating Information Flow

Spring Web flux Interceptors: These interceptors act as intermediaries within the request processing pipeline, enabling you to intercept requests and responses at various stages, perform custom logic, and modify the request or response objects. In context propagation scenarios, interceptors can be used to capture context information from incoming requests (such as headers) and inject it into the reactor context, ensuring it’s readily accessible throughout subsequent processing steps. Think of interceptors as checkpoints where you can examine incoming requests, extract pertinent context information (like authentication tokens or transaction IDs), and make it readily available for downstream services.

WebGraphQL Interceptors: Specifically designed for Spring Boot GraphQL applications, these interceptors offer a means to intercept incoming GraphQL requests and responses, enabling authentication, authorization, data transformation, and other custom processing tasks. They can also be leveraged to propagate context information between the API Gateway and the backend GraphQL service. GraphQL interceptors play a vital role in securing and enriching GraphQL requests before they reach the backend service, while also offering opportunities to propagate context information from the API Gateway to the service.

Illustrative Code Example and Explanation: Demystifying the Flow

In the following example, we will walk through a flow where a GraphQL request is received by a backend service through an API gateway. API gateways typically include a trace ID, request ID, or transaction ID in the header, which is then passed to the backend service. This transaction ID is captured in the interceptor and added to both the Reactor context and MDC. Once the transaction ID is appended in all logs, it becomes much easier to troubleshoot using log analysis tools like Splunk or LogInSight.

Dependencies

<!-- Spring Webflux starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.2.2</version>
</dependency>
<!-- Reactor Core -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.6.2</version>
</dependency>
<!-- Reactor Test -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.6.2</version>
<scope>test</scope>
</dependency>
<!-- Spring GraphQL starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
<version>3.2.2</version>
</dependency>
<!-- Micrometer tracing (Required for observability) -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<version>1.2.2</version>
</dependency>
<!-- Spring Boot Actuator (Required for observability) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.2.2</version>
</dependency>

Main class

@PostConstruct
public void init() {
Hooks.enableAutomaticContextPropagation();
}
Code depicting a custom GraphQL interceptor to get the header and put into MDC and Reactor Context

Explanation:

Main Class

Hooks.enableAutomaticContextPropagation()

  • Ensures that tracing context is automatically propagated across threads and asynchronous boundaries within reactive flows.
  • This is crucial for maintaining a consistent trace throughout the application, even when execution jumps between threads or involves non-blocking operations.
  • Called within a @PostConstruct method to guarantee it's executed early during application startup before any reactive operations begin.

RequestHeaderInterceptor class

1. Header Extraction: The code retrieves the tid header value from the incoming request. This value represents a unique identifier for a specific user session or transaction.

2. MDC/Context Population:

- If the header exists, the transaction ID (extracted from the header) is stored in both MDC and the Reactor context using the ThreadLocalAccessor registered earlier. This ensures that the ID is accessible throughout the thread’s execution and across asynchronous operations facilitated by Reactor.

3. Downstream Processing:

- The request, now enriched with the transaction ID in both MDC and Reactor context, is passed to the next interceptor in the chain using chain.next(request). This allows subsequent components to access the ID for various purposes, such as logging, tracing, or correlation.

4. Reactor Context Enrichment:

- Additionally, the code calls contextWrite(Context.of(TRANSACTION_ID_KEY, tid)) to explicitly add the transaction ID to the Reactor context. This ensures that the ID is readily available within the reactive pipeline facilitated by Spring Web flux, enabling consistent access across asynchronous operations.

logback.xml

Adding %X means all the key value pairs present in MDC will be printed.

To print the value of a particular key in MDC , use %X{key-name}.

<pattern>
[%date{"yyyy-MM-dd'T'HH:mm:ss,SSSZ"}]-[%-5level]-["%thread" %X ]-[%logger{25}]-[%line]-%msg %ex{full}%n
</pattern>

Log output

We can see that txId is getting appended in the logs. Since micrometer is present in the class path , Spring Boot automatically enables tracing , hence traceID and spanId are getting appended in the logs.

Why Pass Context Information in Logs?

Including context information in logs offers a multitude of benefits:

- Enhanced Debugging: Contextual information, like transaction IDs or user IDs, provides valuable clues when debugging issues. By correlating logs with specific transactions or users, developers can swiftly pinpoint the root cause of problems.

- Improved Auditing and Monitoring: Context-rich logs empower detailed activity tracking and auditing. By incorporating user IDs, timestamps, and identifiers for specific requests or operations, organisations can gain deeper insights into system behaviour and user interactions.

- Streamlined Troubleshooting: Imagine encountering an error in a complex micro services architecture. Contextual information in logs acts as a breadcrumb trail, guiding developers through the system’s execution flow, pinpointing the point of failure, and facilitating faster resolution.

Conclusion

Effectively harnessing ThreadLocalAccessor, ContextRegistry, MDC, Reactor Context, and GraphQL Interceptors empowers seamless context propagation within Spring Web flux and GraphQL applications. This collaborative effort fosters efficient information flow, enhances debugging capabilities, and bolsters overall application operability. By incorporating context into logs, developers gain valuable insights and expedite troubleshooting efforts, ensuring a robust and maintainable system.

If you liked the article please take a minute to offer me a clap 👏 or even buy me a coffee https://www.buymeacoffee.com/abhiandy

--

--

Abhishek Anand
Javarevisited

Spring Boot Advocate | Senior Software Engineer @ Intuit | ex-VMware