Java Logging with Mapped Diagnostic Context (MDC)

A Mapped Diagnostic Context, or MDC in short, is an instrument for distinguishing interleaved log output from different sources. Log output is typically interleaved when a server handles multiple clients near-simultaneously.

1. Stamping Requests with MDC

MDC is used to stamp each request. It is done by putting the contextual information about the request into the MDC.

MDC class contain the following static methods:

  • void put(String key, String val): puts a context value as identified by key into the current thread’s context map. We can place as many value/key associations in the MDC as needed.
  • String get(String key): gets the context value identified by the key parameter.
  • void remove(String key): removes the context value identified by the key parameter.
  • void clear(): clears all entries in the MDC.

A sample example of stamping the requests with MDC API is:

MDC.put("sessionId", "abcd");
MDC.put("userId", "1234");

2. Printing MDC Values in Logs

To print the context information in the logs, we can use MDC keys in the log pattern string.

For referring to the MDC context keys, we use the %X specifier that is used to print the current thread’s Nested Diagnostic Context (NDC) and/or Mapped Diagnostic Context (MDC).

  • Use %X to include the full contents of the Map.
  • Use %X{key} to include the specified key.
  • Use %x to include the full contents of the Stack.

For example, we can refer the userId and the sessionId keys created in the first section, as follows. During application runtime, MDC information will be appended to every log message using the given appended.

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 
  <layout>
    <Pattern>%d{DATE} %p %X{sessionId} %X{userId} %c - %m%n</Pattern>
  </layout> 
</appender>

3. Adding MDC using Servet Filter

Putting MDC context information arbitrarily in several places in the application is not a good idea. It can be hard to maintain such code.

Due to MDC being thread-local in nature, we can use the servlet filters as a good place to add MDC logging because Servlets use a single thread to process the whole request. In this way, we can be sure that MDC information is not mixing up with other requests handled by the same handler/controller.

Handling it using framework provided servlet filter also frees us from thread-safety or synchronization concerns because frameworks handle these issues safely and transparently.

Servlet containers are thread per request. When an HTTP request is made, a thread is created or retrieved from a pool to serve it.

So, DO NOT forget to clear the MDC context after the request processing is complete, otherwise when the same thread serves another request next time, they MDC information will be stale and can create wrong log entries.

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.slf4j.MDC;
 
//To convert to a Spring Boot Filter 
//Uncomment @Component and Comment our @WebFilter annotation
//@Component 
@WebFilter( filterName = "mdcFilter", urlPatterns = { "/*" } )
public class MDCFilter implements Filter {
 
    @Override
    public void destroy() {
    }
 
    @Override
    public void doFilter( final ServletRequest request, 
    			final ServletResponse response, final FilterChain chain )
        throws IOException, ServletException {
 
        MDC.put( "sessionId", request.getParameter("traceId") );
 
        try {
            chain.doFilter( request, response );
        } finally {
            MDC.clear();
        }
    }
 
    @Override
    public void init( final FilterConfig filterConfig ) 
    	throws ServletException {
    }
}

Note that in order for Spring framework to recognize this class as a web filter, we need to define it as a bean with the @Component annotation.

4. MDC with Logging Frameworks

4.1. MDC with Log4J2

Log4j2 supports both, the MDC and the NDC, but merges them into a single class ThreadContext. The Thread Context Map is the equivalent of the MDC and the Thread Context Stack is the equivalent of the NDC.

To enable automatic inheritance of copies of the MDC to newly created child threads, enable the “isThreadContextMapInheritable” Log4j system property.

An example of Log4j2 ThreadContext.

import org.apache.logging.log4j.ThreadContext;

//Add information in context
ThreadContext.put("id", UUID.randomUUID().toString());
ThreadContext.put("ipAddress", request.getRemoteAddr());

//To clear context
ThreadContext.clear();

To print the context values, we can use the %X based pattern layout as discussed in the section printing MDC values.

We can read more about ThreadContext and CloseableThreadContext in the official website.

4.2. MDC with SLF4J, Logback and Log4j

MDC with SLF4J is dependent on MDC support by the underlying logging library. If the underlying library does not support MDC then all the MDC-related statements will be skipped without any side effects.

Note that at this time, only two logging systems, namely log4j and logback, offer MDC functionality. For java.util.logging which does not support MDC, BasicMDCAdapter will be used. For other systems, NOPMDCAdapter will be used.

MDC is supported by the above frameworks in the following classes:

  • org.slf4j.MDC (SLF4J and Logback)
  • org.apache.log4j.MDC (Log4j)

An example of SLF4J MDC.

import org.slf4j.MDC;

//Add information in context
MDC.put("id", UUID.randomUUID().toString());
MDC.put("ipAddress", request.getRemoteAddr());

//To clear context
MDC.clear();

Printing the context values, we need to use the %X based pattern layout as discussed previously.

We can read more about MDC support in SLF4J and Logback in the official website.

5. Conclusion

Mapped Diagnostic Context (MDC) is an excellent way to add more context information in the application logs for improved request tracing purposes, specially if the application is a complex distributed application.

But we need to be very careful in using MDC in a concurrent environment where threads are taken from a thread pool. In such a case, it is very important to clear the context information from the thread after the request has been processed.

To keep things simple, it is recommended to use SLF4J based MDC because it is simple to use and it uses the underlying logging framework’s support for MDC logging.

Happy Learning !!

Comments

Subscribe
Notify of
guest
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode