Mastering MDC with Logback: Traceable Logging for Distributed Systems
This article explains how to use SLF4J's MDC with Logback to assign a unique trace ID to each request, propagate it across threads and services, and configure log patterns so that logs become fully traceable for easier debugging in distributed systems.
In distributed systems, service calls often span multiple applications and threads. Because requests frequently switch across threads and asynchronous processing, ordinary logs cannot effectively link a complete call, making troubleshooting difficult. To better trace the full execution path, we need to assign a unique identifier to each request and carry it throughout the call chain, printing it in logs so the whole process can be retrieved.
SLF4J provides MDC (Mapped Diagnostic Context) which stores and propagates context information within the same thread, commonly used for log tracing. This article demonstrates MDC with Logback, its usage, and source code analysis.
Function Overview
MDC's main functions include:
Call chain tracing Propagate request ID across the entire call chain for easier log analysis.
Log enrichment Automatically attach context information such as user ID, order number, etc., to logs.
Thread isolation MDC variables are effective only for the current thread and do not affect other requests.
Cross‑process calls Pass the request ID from the previous service to the next service.
How to Use
Below is a simple demonstration of using MDC; in real projects it should be applied in a logging aspect for uniform handling.
Single‑thread environment
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class MDCDemo {
private static final Logger logger = LoggerFactory.getLogger(MDCDemo.class);
public static void main(String[] args) {
String traceId = request.getHeader(TRACE_ID);
if (traceId == null || traceId.isEmpty()) {
traceId = IdUtil.simpleUUID();
}
MDC.put("TRACE_ID", traceId);
logger.info("This is a test log");
// todo business logic
// clear MDC to avoid thread‑pool contamination
MDC.clear();
}
}To print the call chain, the Logback configuration must include the trace ID pattern.
<property scope="context" name="APP_PATTERN" value='%-5p %d [%t] [%X{TRACE_ID}] %c{50}:%L> %m%n'/>Multi‑thread environment
MDC is effective only for the current thread; in multi‑thread scenarios you need to transfer it manually.
public static void main(String[] args) {
MDC.put("traceId", "67890");
executor.submit(() -> {
// manually transfer MDC
MDC.put("TRACE_ID", MDC.get("traceId"));
logger.info("Child thread log");
MDC.clear();
});
MDC.clear(); // main thread cleanup
executor.shutdown();
}In Spring Boot, a custom decorator can automatically propagate MDC.
public class MDCTaskDecorator {
public static Runnable wrap(Runnable runnable) {
Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
if (context != null) {
MDC.setContextMap(context);
}
try {
runnable.run();
} finally {
MDC.clear(); // cleanup
}
};
}
} public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
MDC.put("traceId", "99999");
executor.submit(wrap(() -> {
System.out.println(Thread.currentThread().getName() + " traceId: " + MDC.get("traceId"));
}));
MDC.clear();
executor.shutdown();
}Source Code Analysis
MDC is based on a ThreadLocal variable, so data is isolated per thread. MDC maintains an MDCAdapter; Logback provides LogbackMDCAdapter implementation.
public class LogbackMDCAdapter implements MDCAdapter {
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal();
public void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} else {
Map<String, String> oldMap = (Map) this.copyOnThreadLocal.get();
Integer lastOp = this.getAndSetLastOperation(1);
if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {
oldMap.put(key, val);
} else {
Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);
}
}
}
// other methods omitted for brevity
}copyOnThreadLocal is a ThreadLocal object; each thread maintains its own MDC data, and get() retrieves values directly from the ThreadLocal variable, ensuring thread safety.
Conclusion
For software development, using MDC with Logback and a traceId is recommended to achieve log tracing and full‑chain analysis.
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.
Lin is Dream
Sharing Java developer knowledge, practical articles, and continuous insights into computer engineering.
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.
