MDC+InheritableThreadLocal和spring cloud sleuth
在微服务架构中,日志链路追踪(Logback + Distributed Tracing) 是一个关键需求,主要用于跟踪请求在不同服务间的调用链路,便于排查问题。常见的实现方案有两种:
手动方案(MDC + InheritableThreadLocal)
自动化方案(Spring Cloud Sleuth + Zipkin/Jaeger)
下面从 Logback 日志集成 的角度,对比这两种方案的实现方式、优缺点及适用场景。
- 手动方案:MDC + InheritableThreadLocal
核心组件
MDC(Mapped Diagnostic Context)
Logback 提供的线程本地存储,用于存放日志变量(如 traceId)。
日志输出时自动携带 MDC 中的字段(需配置 %X{traceId})。
InheritableThreadLocal
解决异步线程(如线程池、@Async)无法继承 MDC 的问题。
实现步骤
(1) 定义 TraceContext(管理 traceId)
public class TraceContext {
private static final InheritableThreadLocal<String> TRACE_ID = new InheritableThreadLocal<>();
public static void setTraceId(String traceId) {
TRACE_ID.set(traceId);
MDC.put("traceId", traceId); // 存入 MDC,Logback 自动输出
}
public static String getTraceId() {
return TRACE_ID.get();
}
public static void clear() {
TRACE_ID.remove();
MDC.remove("traceId");
}
}
(2) 拦截器设置 traceId(HTTP 请求入口)
public class TraceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String traceId = request.getHeader("X-Trace-Id") != null ?
request.getHeader("X-Trace-Id") : UUID.randomUUID().toString();
TraceContext.setTraceId(traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
TraceContext.clear(); // 防止内存泄漏
}
}
(3) Logback 配置(输出 traceId)
<!-- logback-spring.xml -->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
日志示例:
14:25:30.456 [http-nio-8080-exec-1] [abc123] INFO com.example.demo.Controller - Request received
(4) 异步线程支持(线程池需额外处理)
// 普通线程
new Thread(() -> {
log.info("Async task"); // 能继承 traceId
}).start();
// 线程池需使用 TransmittableThreadLocal(阿里开源库)
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(() -> {
log.info("ThreadPool task"); // 默认会丢失 traceId!
});
TransmittableThreadLocal vs InheritableThreadLocal
- 自动化方案:Spring Cloud Sleuth + Logback
核心组件
Spring Cloud Sleuth
自动生成 traceId 和 spanId,并通过 MDC 输出到日志。
支持 HTTP(Feign/RestTemplate)、MQ(Kafka/RabbitMQ)、gRPC 等自动传播。
Logback 集成
Sleuth 自动填充 MDC,无需手动管理。
实现步骤
(1) 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- 可选:上报到 Zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
(2) Logback 配置(自动携带 traceId)
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%X{traceId:-}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
日志示例(Sleuth 自动填充 traceId 和 spanId):
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%X{traceId:-}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
(3) 跨服务调用(自动传播 traceId)
HTTP(Feign):自动添加 X-B3-TraceId Header。
MQ(Kafka):消息头自动携带追踪信息。
-
对比总结
-
推荐选择
简单项目:使用 MDC + InheritableThreadLocal(或 TransmittableThreadLocal)。
微服务架构:直接上 Spring Cloud Sleuth(或 OpenTelemetry),减少维护成本。