Search code examples
javaspring-webfluxproject-reactor

How to correctly use slf4j MDC in spring-webflux WebFilter


I referenced with the blog post Contextual Logging with Reactor Context and MDC but I don't know how to access reactor context in WebFilter.

@Component
public class RequestIdFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        List<String> myHeader =  exchange.getRequest().getHeaders().get("X-My-Header");

        if (myHeader != null && !myHeader.isEmpty()) {
            MDC.put("myHeader", myHeader.get(0));
        }

        return chain.filter(exchange);
    }
}

Solution

  • 2023: Forget logOnNext wrapper. Use Context Propagation

    I started working with Reactor back in 2018, and thus far, there hasn't been a truly good alternative to the wrapper method inside doOnNext where you manually copy your trace fields from Reactor's Context into MDC, making your own ad-hoc bridge between reactive and imperative worlds, and voilà, your logs now could make sense. But things changed, and finally, a new solution is here – Context propagation. Let's take a look at it.

    Imagine that you have your Spring service, and you have a set of fields that define your diagnostic context, the fields you use for tracing the service's activities. Let's assume that you store that set as the following property: management.tracing.baggage.correlation.fields: trace, session.

    Now, to have these fields automatically populated into the MDC of the thread that executes the call of your reactive chain, sourcing the keys-values from the reactive context, you just need to add context-propagation library to your project and after do the following service-wide configuration:

    /**
     * 1. Will register ThreadLocalAccessors into ContextRegistry for fields listed in application.yml as property value
     * <b>management.tracing.baggage.correlation.fields</b>
     * 2. Enables Automatic Context Propagation for all reactive methods
     *
     * @see <a href="https://github.com/micrometer-metrics/context-propagation">context-propagation</a>
     */
    @Configuration
    @ConditionalOnClass({ContextRegistry.class, ContextSnapshotFactory.class})
    @ConditionalOnProperty(value = "management.tracing.baggage.correlation.fields", matchIfMissing = true)
    public class MdcContextPropagationConfiguration {
    
        public MdcContextPropagationConfiguration(@Value("${management.tracing.baggage.correlation.fields}")
                                                  List<String> fields) {
            if (!isEmpty(fields)) {
                fields.forEach(claim -> ContextRegistry.getInstance()
                                                       .registerThreadLocalAccessor(claim,
                                                                                    () -> MDC.get(claim),
                                                                                    value -> MDC.put(claim, value),
                                                                                    () -> MDC.remove(claim)));
                return;
            }
    
            Hooks.enableAutomaticContextPropagation();
        }
    }
    

    The trick here is to use Hooks.enableAutomaticContextPropagation(). As soon as we register a set of ThreadLocalsAccessors for propagation that maps out the tracing fields, the hook will ensure the passing on of the values under the keys of the registered fields from the reactive context into MDC on each call of your chain. These fields within MDC subsequently could be referenced in slf4j appenders in any preference.

    That's it.