Java中用MDC实现链路追踪的核心是生成、绑定、透传唯一traceId:入口处从请求头读取或生成并MDC.put,日志模板引用%X{traceId},多线程需通过TaskDecorator或setContextMap透传,跨服务调用须通过HTTP头(如X-Trace-Id)传递。

Java 中用 MDC 实现链路追踪,核心是让每次请求携带一个唯一 traceId,并贯穿整个处理过程——从入口、内部调用、异步任务到跨服务通信。它不依赖复杂中间件,轻量但必须做对三件事:生成、绑定、透传。
一、在请求入口生成并绑定 traceId
每个 HTTP 请求进来时,应分配一个全局唯一标识。推荐在 Filter 或 Interceptor 中统一处理:
- 优先从请求头(如 X-Trace-Id)读取,方便上游服务传递
- 若无,则用 UUID.randomUUID().toString() 生成(去横线更简洁)
- 调用 MDC.put("traceId", traceId) 绑定到当前线程
- 务必在 finally 块中执行 MDC.remove("traceId") 或 MDC.clear(),防止线程复用污染后续日志
二、配置日志模板自动打印 traceId
Logback(或 Log4j2)需在 pattern 中引用 MDC 值。例如 logback-spring.xml 中:
<pattern>%d{HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{20} - %msg%n</pattern>这样每条日志开头就会带方括号包裹的 traceId,比如:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
14:23:01.123 [a1b2c3d4] [http-nio-8080-exec-1] INFO c.n.o.OrderService - 创建订单 ORDER-789三、解决 MDC 在多线程场景下丢失的问题
MDC 基于 ThreadLocal,天然不跨线程。线程池、CompletableFuture、@Async 等都会导致子线程拿不到 traceId。必须显式透传:
- 线程池任务:提交前用 MDC.getCopyOfContextMap() 拍照,执行时调用 MDC.setContextMap() 还原
- Spring 环境推荐方式:给 ThreadPoolTaskExecutor 设置 setTaskDecorator,注入自定义 MdcTaskDecorator,自动包装所有任务
- CompletableFuture:禁用默认 ForkJoinPool,改用已装饰的线程池,如 supplyAsync(() -> ..., traceableExecutor)
- 定时任务(@Scheduled):每次执行都是新线程,需在方法内重新生成或从上下文提取 traceId 并 put 进 MDC
四、跨服务调用时传递 traceId
下游服务要继续链路,就必须把 traceId 作为 HTTP 头透出:
- 使用 RestTemplate 时,在请求头中添加:headers.set("X-Trace-Id", MDC.get("traceId"))
- Feign 客户端可通过 RequestInterceptor 自动注入
- OpenFeign + Spring Cloud 默认支持 spring.sleuth.enabled=false 时仍可手动透传,避免引入全链路追踪框架

















