Class MDCInterceptor

  • All Implemented Interfaces:
    ExecInterceptor

    public final class MDCInterceptor
    extends Object
    implements ExecInterceptor
    An execution interceptor that adds support for SLF4J's Mapped Diagnostic Context (MDC) feature.

    The MDC is a set of key-value pairs (i.e. map) that can be implicitly added to all logging statements within the context. The term “context” here comes from SLF4J's lexicon and does not refer to Ratpack's Context. It refers to a logical sequence of execution (e.g. handling of a request). SLF4J's default strategy for MDC is based on a thread-per-request model, which doesn't work for Ratpack applications. This interceptor maps SLF4J's notion of a “context” to Ratpack's notion of an “execution”. This means that after installing this interceptor, the MDC API can be used naturally.

    Please be sure to read the SLF4J manual section on MDC, particularly about how the actual logging implementation being used must support MDC. If your logging implementation doesn't support MDC (e.g. slf4j-simple) then all of the methods on the MDC API become no-ops.

    The interceptor should be added to the server registry, so that it automatically is applied to all executions. The following example shows the registration of the interceptor and MDC API usage.

    
     import java.util.List;
     import java.util.ArrayList;
     import ratpack.test.embed.EmbeddedApp;
     import ratpack.exec.Blocking;
     import org.slf4j.MDC;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
    
     import static org.junit.jupiter.api.Assertions.assertEquals;
    
     import ratpack.core.logging.MDCInterceptor;
    
     public class Example {
    
       private static final Logger LOGGER = LoggerFactory.getLogger(Example.class);
    
       public static void main(String... args) throws Exception {
         EmbeddedApp.of(s -> s
           .registryOf(r -> r.add(MDCInterceptor.instance()))
           .handler(r ->
             ctx -> {
               // Put a value into the MDC
               MDC.put("clientIp", ctx.getRequest().getRemoteAddress().getHost());
               // The logging implementation/configuration may inject values from the MDC into log statements
               LOGGER.info("about to block");
               Blocking.get(() -> {
                 // The MDC is carried across asynchronous boundaries by the interceptor
                 LOGGER.info("blocking");
                 return "something";
               }).then(str -> {
                 // And back again
                 LOGGER.info("back from blocking");
                 ctx.render("ok");
               });
             }
           )
         ).test(httpClient ->
           assertEquals("ok", httpClient.getText())
         );
       }
     }
     

    Given the code above, using the Log4j bindings with configuration such as:

     
     <Console name="Console" target="SYSTEM_OUT">
       <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg - [%X{clientIp}] %n"/>
     </Console>
     
     

    The client IP address will be appended to all log messages made while processing requests.

    Inheritance

    The MDC is not inherited by forked executions (e.g. Execution.fork()). If you wish context to be inherited, you must do so explicitly by capturing the variables you wish to be inherited (i.e. via MDC.get(String)) as local variables and then add them to the MDC (i.e. via MDC.put(String, String)) in the forked execution.

    See Also:
    ExecInterceptor
    • Method Detail

      • instance

        public static MDCInterceptor instance()
        Creates an interceptor with no initialisation action.
        Returns:
        an interceptor with no initialisation action.
        See Also:
        withInit(Action)
      • withInit

        public static MDCInterceptor withInit​(Action<? super Execution> init)
        Creates an interceptor with the given initialisation action.

        The given action will be executed before the first segment of each execution, allowing the MDC to be primed with initial values.

        The following demonstrates priming the MDC with the RequestId.

        
         import org.slf4j.MDC;
         import ratpack.core.handling.RequestId;
         import ratpack.core.logging.MDCInterceptor;
         import ratpack.test.embed.EmbeddedApp;
        
         import static org.junit.jupiter.api.Assertions.assertEquals;
        
         public class Example {
           public static void main(String... args) throws Exception {
             EmbeddedApp.of(s -> s
                 .registryOf(r -> r
                     .add(MDCInterceptor.withInit(e ->
                         e.maybeGet(RequestId.class).ifPresent(requestId ->
                             MDC.put("requestId", requestId.toString())
                         )
                     ))
                 )
                 .handlers(c -> c
                     .get(ctx -> ctx.render(MDC.get("requestId")))
                 )
             ).test(http ->
                 // The default request ID generator generates UUIDs (i.e. 36 chars long)
                 assertEquals(http.getText().length(), 36)
             );
           }
         }
         

        If no initialisation is required, use instance().

        Parameters:
        init - the initialisation action
        Returns:
        an MDCInterceptor
        Since:
        1.1
      • intercept

        public void intercept​(Execution execution,
                              ExecInterceptor.ExecType execType,
                              Block executionSegment)
                       throws Exception
        Description copied from interface: ExecInterceptor
        Intercepts the execution of an execution segment.

        The execution segment argument represents a unit of work of the execution.

        Implementations MUST invoke execute() on the given execution segment block.

        Specified by:
        intercept in interface ExecInterceptor
        Parameters:
        execution - the execution that this segment belongs to
        execType - indicates whether this segment is execution on a compute or blocking thread
        executionSegment - the execution segment that is to be executed
        Throws:
        Exception - any