How to Trace Multi‑Threaded Requests with SLF4J MDC in Java
This article explains how to use SLF4J's MDC feature to attach a request ID to log entries, enabling fast filtering of all logs belonging to a single request even across asynchronous threads and thread pools, and shows a decorator‑based solution for proper propagation.
When a production issue occurs, we often need to collect all logs generated during a request to locate the problem. Filtering by thread ID works for single‑threaded requests, but fails when the request involves asynchronous processing.
Core Implementation
SLF4J provides the MDC (Mapped Diagnostic Context) utility, which allows us to bind a request ID to the logging context.
public class Main {
private static final String KEY = "requestId";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
// Entry: set request ID
MDC.put(KEY, UUID.randomUUID().toString());
// Log in main thread
logger.debug("log in main thread 1");
logger.debug("log in main thread 2");
logger.debug("log in main thread 3");
// Exit: remove request ID
MDC.remove(KEY);
}
}Running the program with a properly configured log4j2.xml produces output such as:
2018-02-17 13:19:52.606 {requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0} [main] DEBUG cn.wudashan.Main - log in main thread 1
2018-02-17 13:19:52.609 {requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0} [main] DEBUG cn.wudashan.Main - log in main thread 2
2018-02-17 13:19:52.609 {requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0} [main] DEBUG cn.wudashan.Main - log in main thread 3The curly‑braced part contains the mapped request ID, which becomes the key for locating all related logs. By inserting MDC.put() at the entry point and MDC.remove() at the exit, we can later filter logs with a simple grep requestId=xxx *.log command.
Advanced: MDC in Asynchronous Threads
In an asynchronous scenario, the request ID does not appear in logs because MDC relies on ThreadLocal , which is confined to the thread where MDC.put() was called.
public class Main {
private static final String KEY = "requestId";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
MDC.put(KEY, UUID.randomUUID().toString());
logger.debug("log in main thread");
new Thread(new Runnable() {
@Override
public void run() {
logger.debug("log in other thread");
}
}).start();
MDC.remove(KEY);
}
}Running this code shows that the asynchronous thread prints an empty MDC context.
To propagate the MDC context to asynchronous execution, we can apply the decorator pattern and create an MDCRunnable that copies the context map.
public class MDCRunnable implements Runnable {
private final Runnable runnable;
private final Map<String, String> map;
public MDCRunnable(Runnable runnable) {
this.runnable = runnable;
this.map = MDC.getCopyOfContextMap();
}
@Override
public void run() {
for (Map.Entry<String, String> entry : map.entrySet()) {
MDC.put(entry.getKey(), entry.getValue());
}
runnable.run();
for (Map.Entry<String, String> entry : map.entrySet()) {
MDC.remove(entry.getKey());
}
}
}Using MDCRunnable to wrap the original Runnable ensures the request ID is available in both a raw thread and a thread‑pool.
public class Main {
private static final String KEY = "requestId";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
MDC.put(KEY, UUID.randomUUID().toString());
logger.debug("log in main thread");
new Thread(new MDCRunnable(new Runnable() {
@Override
public void run() {
logger.debug("log in other thread");
}
})).start();
EXECUTOR.execute(new MDCRunnable(new Runnable() {
@Override
public void run() {
logger.debug("log in other thread pool");
}
}));
EXECUTOR.shutdown();
MDC.remove(KEY);
}
} 2018-03-04 23:44:05.343 {requestId=5ee2a117-e090-41d8-977b-cef5dea09d34} [main] DEBUG cn.wudashan.Main - log in main thread
2018-03-04 23:44:05.346 {requestId=5ee2a117-e090-41d8-977b-cef5dea09d34} [Thread-1] DEBUG cn.wudashan.Main - log in other thread
2018-03-04 23:44:05.347 {requestId=5ee2a117-e090-41d8-977b-cef5dea09d34} [pool-2-thread-1] DEBUG cn.wudashan.Main - log in other thread poolCongratulations! The request ID now appears in logs from the main thread, asynchronous thread, and thread‑pool, enabling reliable log filtering for any request.
By leveraging MDC together with AOP to inject the request ID into all relevant points, developers can dramatically reduce debugging time during development and accelerate log analysis in production environments.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
