为了实现一个链路追踪功能,并确保所有打印的日志中都包含 traceId,可以使用 Spring Cloud Sleuth。Sleuth 是一个分布式追踪解决方案,它可以帮助你跟踪请求在微服务架构中的流动,并将日志关联到同一个请求中。
以下是实现步骤:
1. 添加依赖
首先,在你的 pom.xml 文件中添加 Spring Cloud Sleuth 和 Zipkin(可选)的依赖。Zipkin 是一个收集和查看追踪数据的工具,如果你需要可视化追踪数据,可以一并引入。
org.springframework.cloudspring-cloud-starter-sleuth
org.springframework.cloudspring-cloud-starter-zipkin
2. 配置应用
在 application.yml 或 application.properties 中进行配置,以启用 Sleuth 和 Zipkin(如果使用)。
spring:
sleuth:
sampler:
probability: 1.0 # 设置采样率为100%
zipkin:
base-url: http://localhost:9411 # 如果使用 Zipkin,请指定 Zipkin 服务器地址
3. 自定义日志格式
为了让所有的日志都包含 traceId,你需要自定义日志格式。可以在 logback-spring.xml 或 logback.xml 中配置日志格式。
%d{yyyy-MM-dd HH:mm:ss} \[%thread\] %-5level %logger{36} - traceId=%X{X-B3-TraceId}, spanId=%X{X-B3-SpanId} - %msg%n
4. 确保线程继承 traceId
默认情况下,Sleuth 已经会为每个请求分配唯一的 traceId 和 spanId,并且这些信息会在同一个线程中传递。但是,如果你的应用中有异步任务或线程池,你需要确保这些线程也能够继承 traceId 和 spanId。
你可以通过以下方式确保线程继承 traceId:
4.1 使用 @Async注解时
如果你使用了 @Async 注解来启动异步任务,默认情况下,Spring 的 TaskExecutor 不会自动传播 traceId。你可以通过配置 TaskDecorator 来解决这个问题。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import brave.propagation.TraceContext;
import brave.propagation.CurrentTraceContext;
import brave.spring.web.TracingAsyncUtils;
@Configuration
public class AsyncConfig implements AsyncConfigurer {
private final CurrentTraceContext currentTraceContext;
public AsyncConfig(CurrentTraceContext currentTraceContext) {
this.currentTraceContext = currentTraceContext;
}
@Override public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.initialize();
// Ensure trace context is propagated to async threads
return TracingAsyncUtils.wrap(executor, currentTraceContext);
}
}
4.2 使用线程池时
如果你使用了 ExecutorService 或其他线程池,可以通过 CurrentTraceContext 来确保线程池中的线程也能够继承 traceId。
import brave.propagation.CurrentTraceContext;
import brave.propagation.TraceContext;
public class TracingRunnable implements Runnable {
private final CurrentTraceContext currentTraceContext;
private final Runnable delegate;
public TracingRunnable(CurrentTraceContext currentTraceContext, Runnable delegate) {
this.currentTraceContext = currentTraceContext;
this.delegate = delegate;
}
@Override public void run() {
TraceContext context = currentTraceContext.get();
try (CurrentTraceContext.Scope scope = currentTraceContext.newScope(context)) {
delegate.run();
}
}
}
// 在创建线程池时使用
ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
executor.submit(new TracingRunnable(currentTraceContext, yourRunnable));
5. 测试链路追踪
启动应用后,访问任意接口,检查日志输出。你应该能看到每条日志中都包含了 traceId 和 spanId,并且这些信息在整个请求链路中保持一致。
6. 可视化追踪数据(可选)
如果你启用了 Zipkin,可以通过访问 Zipkin UI (http://localhost:9411) 查看详细的追踪数据,包括请求的时间、调用的服务等。
通过以上步骤,你可以确保所有的日志中都包含 traceId,并且能够完整地追踪一个接口请求的过程,即使是在多线程环境中也能保证追踪信息的传递。