Java Logging with Mapped Diagnostic Context (MDC)
By: Eranda Rajapakshe
Tags: Java, Spring Boot, Logging, Spring, Mdc

Intro

In most of the modern client-server applications, servers are made to handle different client requests in separate worker threads. But traditional logging methods are not able to capture the thread/event specific information without doing a complete logging implementation manually.

MDC is introduced to serve this gap. It will help the developers to store thread/event specific information in its internal data structure (Map). And append the information to the log messages according to the configuration.

Simple Logging Facade for Java (SLF4J) APIs supports MDC feature and through Log4j and LOGBack implementations, developers can use the MDC feature. I will mainly focus on LOGBack in this post.

Internals

It's better to know the internals of the MDC for advanced users as we need to be bit safe when using MDC with thread pools.

MDC implementation is nothing but a ThreadLocal Map and a set of static methods to access it. Mainly,

put

get

remove

clear

Special points

Since the map is ThreadLocal, the developer doesn’t need to worry about any synchronization issues. But developer should clear the MDC context at the execution start point as it will contain garbage values if the threads are being reused. (eg: executer service with reusable threads)

Also, LOGBack documentation mentions that child threads will not inherit parents diagnostic context (MDC Map) and therefore it should be manually moved. For that developers can use getCopyOfContextMap method and setContextMap method in MDC.

Example

I have created a simple example of using MDC with Spring. In this example, I have registered a Spring Interceptor which will get invoked on every inbound request. And that interceptor will inject a unique request Id into its MDC. Using this we will be able to track all the logs related to each particular request/response.

It's important that to clear the MDC which initialization of each request. Below is the interceptor implementation.

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

UUID requestId = UUID.randomUUID();

MDC.clear();

MDC.put("requestId", String.valueOf(requestId));

return true;

}

Also, I have included following content into the logback.xml in the resources directory. This will be the settings file for LOGBack.

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

<encoder>

<pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg %mdc%n</pattern>

</encoder>

</appender>

<root level="info">

<appender-ref ref="STDOUT" />

</root>

</configuration>

Complete implementation is available here. [3]

You can run it as a Springboot application and, use the following example URL to invoke.

It will generate logs similar to below,

29-01-2019 11:07:03.129 [http-nio-8080-exec-1] INFO e.b.e.controllers.PersonController.getPerson - Request received at the controller. PersonId: 123 requestId=c6acd0ff-9c6a-4153-a12b-42f0100509a4 29-01-2019 11:07:03.176 [http-nio-8080-exec-1] INFO e.b.e.services.PersonInfoService.getPersonInfo - Person info retrieved. Content: {"id":"123","name":"John","age":27} requestId=c6acd0ff-9c6a-4153-a12b-42f0100509a4

Note that requestId is logged in each log message.

References

[1]. https://www.slf4j.org/manual.html#mdc

[2]. https://logback.qos.ch/manual/mdc.html

[3]. https://github.com/erandacr/mdc-exmaple