MDC (Mapped Diagnostic Context) 是 SLF4J 提供的線程級診斷上下文映射表。它允許你在當前執行線程中綁定鍵值對(如 traceId、userId、spanId),日誌框架(Logback/Log4j2)在輸出時自動解析並注入到日誌模板,實現無侵入式的全鏈路日誌貫穿。
傳統日誌打印需手動拼接參數:
LOG.debug("userId={}, orderId={} 處理訂單", userId, orderId); // 易遺漏、格式不統一
MDC 方式:一次綁定,處處可用:
MDC.put("traceId", TraceUtils.generate());
MDC.put("userId", userId);
LOG.debug("處理訂單"); // 自動輸出 traceId、userId
MDC.put(key, val):放入鍵值對(值必須為 String)MDC.get(key):獲取值MDC.remove(key):移除單個鍵MDC.clear():清空當前線程上下文(必須在 finally 中調用,防止線程池復用導致數據洩漏)MDC.getCopyOfContextMap():獲取不可變副本,用於跨線程傳遞@Component
public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String traceId = RequestHolder.getTraceId(); // 從 Header 或生成
try {
MDC.put("traceId", traceId);
chain.doFilter(req, res);
} finally {
MDC.remove("traceId"); // 關鍵:防止線程池污染
}
}
}
使用 ThreadPoolTaskExecutor 配合 TaskDecorator 自動複製 MDC:
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(runnable -> {
Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
try { MDC.setContextMap(context); runnable.run(); }
finally { MDC.clear(); }
};
});
return executor;
}
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId:[%X{traceId}] - %msg%n</pattern>
</encoder>
</appender>
關鍵點:%X{traceId} 會自動從 MDC 取值,若不存在則輸出空字串,不影響日誌格式。
tenantId,日誌自動分類存儲。bizType=order_pay,便於 ELK 多維度檢索。finally 調用 MDC.clear() 或 remove(key)。put/remove 會有微小鎖競爭,核心鏈路可接受,非核心路徑慎用。MDC 是「零代碼侵入」實現分佈式日誌追蹤的利器。結合 AOP/Filter/TaskDecorator 在邊界統一治理上下文生命週期,配合 Logback %X{key} 模板,即可構建清晰可查的全鏈路可觀測體系。