Logback MDC 实现全链路日志追踪的核心是请求入口生成唯一 traceId 并贯穿同步/异步/线程池调用,通过拦截器注入与清理、手动透传子线程上下文、HTTP Header 跨服务传递,配合统一日志格式与可观测性实践保障 traceId 全程不丢失、不串号。

用 Logback 的 MDC 实现分布式全链路日志追踪,核心是让每个请求从入口开始携带唯一 traceId,并贯穿所有同步调用、异步任务、线程池操作,最终在日志中自动输出。它不依赖外部组件,轻量、低侵入,但必须处理好上下文传递和清理。
一、基础配置:注入 traceId 并格式化日志
MDC 本质是基于 ThreadLocal 的线程级 Map,需在请求入口生成并写入,在日志模板中引用:
- 定义拦截器(如
MdcInterceptor),在preHandle中生成 traceId(例如UUID.randomUUID().toString().replace("-", "")),调用MDC.put("traceId", traceId) - 在
afterCompletion中执行MDC.clear()或MDC.remove("traceId"),避免线程复用导致脏数据 - 修改
logback-spring.xml的 pattern:%d{HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{20} - %msg%n
其中%X{traceId}会自动读取当前线程 MDC 中的值
二、解决子线程/线程池中 traceId 丢失
MDC 不会自动跨线程继承,CompletableFuture、@Async、自定义线程池等场景下,子线程的 MDC 是空的。必须手动透传:
- 在提交任务前,获取当前上下文:
Mapcontext = MDC.getCopyOfContextMap(); - 包装 Runnable/Callable,执行前恢复上下文:
return () -> { MDC.setContextMap(context); doWork(); }; - 对
ThreadPoolTaskExecutor,可重写execute和submit方法,或使用TaskDecorator自动完成上下文复制
三、跨服务调用时保持 traceId 透传
单体应用内靠 MDC 就够了;微服务间需靠 HTTP Header 传递,再由下游服务重新注入 MDC:
- 上游服务发请求时,把
MDC.get("traceId")写入 Header,如trace-id: a1b2c3d4 - 下游服务通过 Filter 或 Interceptor 拦截请求,读取该 Header:
若存在则直接MDC.put("traceId", headerValue);
若不存在则新生成一个(保证日志不为空) - 推荐统一约定 Header 名,如
X-Trace-ID,与 MDC key("traceId")保持一致,减少映射出错
四、补充建议:提升稳定性与可观测性
仅靠基础用法容易踩坑,以下实践能显著降低维护成本:
- 不要在业务代码里反复调用
MDC.put,全部交由拦截器/Filter 管理,避免遗漏或重复 - 日志模板中可同时加入
%X{userId}、%X{reqId}等字段,丰富排查维度 - 配合 ELK 或 Loki 收集日志时,确保日志行是单行结构(避免换行干扰解析),且 traceId 字段为独立 keyword 类型,便于聚合查询
- 上线前用压测工具模拟高并发+异步混合调用,验证 traceId 是否全程不丢失、不串号










