Java项目中MDC实现链路追踪需三步:请求入口生成并绑定traceId、日志格式配置%X{traceId}自动输出、线程池等跨线程场景手动透传MDC上下文,缺一不可。

Java 项目中用 MDC 实现链路追踪 ID 的传递,核心是三件事:在请求入口生成并绑定 traceId、让日志格式自动输出它、确保跨线程(比如线程池)不丢上下文。不是加个 put 就完事,漏掉清理或透传,日志就乱套了。
HTTP 请求入口统一注入 traceId
所有 Web 请求都从 Filter 或 Interceptor 开始,这是埋 traceId 的黄金位置:
- 检查请求头(如 X-Trace-Id),有就复用;没有就生成唯一 ID(推荐
IdUtil.fastSimpleUUID()或UUID.randomUUID().toString()截取前16位) - 调用
MDC.put("traceId", traceId)绑定到当前线程 - 务必在
finally块里执行MDC.remove("traceId"),避免线程复用导致 traceId 污染后续请求
日志配置里显式引用 traceId
Logback 配置(logback-spring.xml)中,在 pattern 里加上 %X{traceId} 占位符:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
这样每条日志开头自动带方括号包裹的 traceId,无需在每个 log.info() 里手动拼接。
线程池任务必须手动透传 MDC
Java 线程池不会自动继承主线程的 MDC,因为底层依赖 ThreadLocal,而线程复用后上下文为空或残留旧值:
- 提交任务前,用
MDC.getCopyOfContextMap()拷贝当前上下文 - 包装 Runnable/Callable:构造时保存该 map,执行时先
MDC.setContextMap(map),再调原逻辑,最后MDC.clear() - 更推荐 Spring 的
ThreadPoolTaskExecutor+TaskDecorator方式,全局生效,不用每个 submit 都手写包装
别漏掉异步和定时场景
CompletableFuture 和 @Scheduled 定时任务最容易断链:
-
supplyAsync(..., executor)必须传入已装饰的线程池,禁用默认 ForkJoinPool - @Scheduled 方法开头要主动判断:
if (MDC.get("traceId") == null) MDCTraceUtils.addTraceId(); - Dubbo/Feign 调用下游时,需在拦截器中把
MDC.get("traceId")写入请求头,下游再读取绑定

















