java日志结合mdc实现分布式链路追踪的核心是用唯一traceid贯穿请求生命周期并自动注入日志。通过logback/log4j2配置%x{traceid}占位符、请求入口生成注入、http头跨服务传递、异步场景手动传递或使用transmittablethreadlocal解决mdc断裂问题。

Java 日志结合 MDC 实现分布式链路追踪,核心是用一个唯一 traceId 贯穿请求生命周期,并自动注入到每条日志中。它不依赖复杂 APM 工具,轻量、低侵入、见效快。
配置日志模板,让 traceId 自动出现在每行日志里
在 Logback(或 Log4j2)配置文件中,修改 pattern,加入 %X{traceId} 占位符:
只要当前线程的 MDC 中存在 key 为 "traceId" 的值,该位置就会自动渲染出来。无需改任何业务日志语句,原有 logger.info(...) 全部生效。
在请求入口生成并注入 traceId
对 Web 应用,最常用方式是通过 Filter 或 Spring 拦截器统一处理:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 从 HTTP 请求头(如 X-Trace-ID)读取已有 traceId;若不存在,就用 UUID 或 Snowflake 生成新的
- 调用 MDC.put("traceId", traceId) 绑定到当前线程上下文
- 把 traceId 写回响应头,方便前端或网关透传
- 请求结束后务必调用 MDC.clear(),防止线程池复用导致上下文污染
跨服务传递 traceId,靠 HTTP 头 + 客户端拦截
下游服务无法“看到”上游的 MDC,必须显式传递。关键动作有两步:
- 调用方(如 Feign、RestTemplate)在发起远程请求前,从 MDC 获取 traceId,并放入请求头:headers.set("X-Trace-ID", MDC.get("traceId"))
- 被调用方在 Filter/Interceptor 中读取该 header 并再次 put 进 MDC,完成接力
这样,一次跨服务调用的 traceId 就能串起来。多个服务的日志都带相同 traceId,查问题时按这个 ID 过滤即可。
异步和线程池场景下避免 MDC 断裂
MDC 基于 ThreadLocal,子线程默认拿不到父线程的上下文。常见断裂点包括:
- CompletableFuture.runAsync()、@Async 方法
- 自定义线程池提交任务
- 虚拟线程(Java 21+)执行任务
解决方法是手动捕获并还原上下文:
- 提交任务前,调用 MDC.getCopyOfContextMap() 获取当前上下文副本
- 在线程内执行前,调用 MDC.setContextMap(mdcContext)
- 执行完记得 MDC.clear(),尤其在 finally 块中
更彻底的方案是使用 Alibaba 的 TransmittableThreadLocal 替换 MDC 底层实现,配合 TtlRunnable 包装任务,可做到自动继承。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










