Spring Cloud Sleuth工作机制及库调用追踪问题咨询
嘿,我来帮你梳理下Spring Cloud Sleuth的相关问题,刚好之前踩过类似的坑😉
简单来说,Sleuth的核心是靠ThreadLocal在同一个请求线程里传递追踪标识——也就是traceId(整个链路的唯一ID)和spanId(每个调用节点的ID)。它会通过各种拦截器自动埋点:
- 当请求进入Spring应用时,Servlet拦截器会创建一个根Span,把
traceId和spanId存入ThreadLocal; - 如果你用RestTemplate/WebClient调用其他服务,Sleuth的客户端拦截器会把这些标识塞进请求头(比如
X-B3-TraceId、X-B3-SpanId)传递给下游; - 同时,它会自动把这些ID注入到日志里,让你能轻松串联整个调用链路的日志。
当然支持,但得看你的调用方式:
- 如果是同一个线程内的本地库方法调用:Sleuth默认不会自动生成子Span(因为它默认只拦截HTTP、消息队列这类跨边界的调用),但可以手动或通过AOP配置来追踪;
- 如果是通过HTTP调用外部库服务:只要客户端和服务端都正确集成Sleuth,就能自动追踪整个链路。
你得先明确自己是哪种调用场景,下面分两种常见情况给你解决方案:
场景1:本地调用库B的Controller方法(库B是Jar依赖,和项目A在同一个应用内)
如果项目A是直接注入库B的Controller然后调用其方法(比如@Autowired LibraryBController后直接调用bEndpoint()),这属于本地方法调用,Sleuth不会自动创建子Span。这时候有两种办法:
方法1:手动创建Span追踪单个调用
用Sleuth提供的Tracer类手动生成子Span:
import brave.Tracer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ProjectAController { @Autowired private Tracer tracer; @Autowired private LibraryBController libraryBController; @GetMapping("/a-endpoint") public String aEndpoint() { // 创建一个名为"call-library-b-controller"的子Span try (Tracer.SpanInScope span = tracer.nextSpan().name("call-library-b-controller").start().scope()) { return libraryBController.bEndpoint(); } } }
方法2:用AOP自动追踪所有库B方法调用
如果想让所有对库B Controller的调用都自动生成Span,可以用Spring AOP做一个切面:
import brave.Tracer; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Aspect @Component public class LibraryBTracingAspect { @Autowired private Tracer tracer; // 匹配库B Controller下的所有方法,根据你的包路径调整 @Around("execution(* com.example.libraryb.controller..*(..))") public Object traceLibraryBCalls(ProceedingJoinPoint joinPoint) throws Throwable { String spanName = "library-b-" + joinPoint.getSignature().getName(); try (Tracer.SpanInScope span = tracer.nextSpan().name(spanName).start().scope()) { return joinPoint.proceed(); } } }
记得要引入Spring AOP的依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
场景2:通过HTTP请求调用库B的Controller(库B是独立服务)
如果是这种情况,需要确保两个关键点:
1. 项目A的HTTP客户端被Sleuth拦截
对于RestTemplate:如果你是用@Bean或RestTemplateBuilder创建的,Sleuth会自动添加追踪拦截器。但如果是手动new RestTemplate(),得手动加拦截器:
import brave.spring.web.TraceClientHttpRequestInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(TraceClientHttpRequestInterceptor traceInterceptor) { RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add(traceInterceptor); return restTemplate; } }
对于WebClient,Sleuth也会自动配置,只要你用WebClientBuilder创建实例就行。
2. 库B集成Spring Cloud Sleuth
在库B的依赖中添加Sleuth starter:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
同时,确保库B的日志配置里包含traceId和spanId,比如logback.xml的日志格式:
<encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %X{traceId:-} %X{spanId:-}%n</pattern> </encoder>
这样库B就能接收项目A传递的追踪标识,把自己的调用节点加入整个链路了。
内容的提问来源于stack exchange,提问作者Kiki

