How can the MDC context be used in the reactive Spring applications
A lot of SpringBoot 1.x application used the slf4j MDC context in order to enrich logging messages with request specific parameters. With the release of the SpringBoot 2.x and Spring WebFlux, using MDC context with reactive web-applications is not working anymore out-of-the-box. This blog describes how can you overcome this problem and provides a Kotlin-based example.
The slf4j library and its underlying implementations like logback offer a concept of the Mapped Diagnostic Context. In the multi-threaded systems, where each user request is handled by a separate thread, the MDC context enables us to produce different logging output for each request. We can use this technique to include session id, request id or actually any request specific property to each log message outputted during the execution of that specific request.
The standard way of using the MDC is to set a context value bounded to a specific key. Later on, we can reference that same key in our logging configuration and the logging framework will print the value that is available in the MDC context for a thread running the request.
This technique works well as long as a single request is completely processed by one thread, as the MDC uses the Java ThreadLocal to store the values and separate them between threads. However, Spring 5 comes with the Spring WebFlux that uses Spring Reactor and Netty to handle requests in a reactive way. In the reactive non-blocking world, a request could be processed by multiple threads. This means that setting the MDC context at the beginning of the request is not anymore an option.
In order to continue using the MDC feature in the reactive Spring application, we need to make sure that whenever a thread starts processing a request it has to update the state of the MDC context. This can be done by doing two things:
All the values that we previously added directly to the MDC context should now be added to Reactor context. The framework will ensure that the reactive context is passed along the reactive execution and it will not be bounded to any specific thread.
Ensure that values residing in the reactive context are copied to the MDC context whenever there is a possibility that a thread that processes the request has changed. For this, we will implement the CoreSubscriber that will be hooked into the Reactor using Hooks.
CoreSubscriber implementation that is hooked onEachOperator
With the above configuration in place, the only thing left is to add context values to a reactive sequence. Once the sequence is materialized the MDC context will be properly updated, so any logging statement we produce in the userService will correctly log the sessionId that was passed along.