使用new创建RestTemplate时无法传递Sleuth TraceId和SpanId至下游服务
问题分析与解决方案
你的自定义拦截器存在的核心问题
- 未创建子Span:Brave官方拦截器会为外部HTTP调用生成独立的子Span,将子Span的ID作为
X-B3-SpanId传递,同时把当前服务的SpanID作为X-B3-ParentSpanId。你直接复用当前Span的ID作为下游的SpanId,会导致链路层级断裂,下游服务无法正确识别父Span关联关系,甚至生成新的TraceId。 - 可能存在拦截器未正确绑定的情况:如果仅注入了拦截器,但未手动将其添加到
RestTemplate的拦截器列表中,拦截器根本不会生效。
正确的实现方式
方式一:直接使用Sleuth官方提供的拦截器(推荐)
无需自定义,直接注入官方的TracingClientHttpRequestInterceptor,手动添加到new出来的RestTemplate中即可:
@RestController public class YourController { private final TracingClientHttpRequestInterceptor sleuthInterceptor; // 构造注入官方拦截器 public YourController(TracingClientHttpRequestInterceptor sleuthInterceptor) { this.sleuthInterceptor = sleuthInterceptor; } @GetMapping("/call-ms2") public String callMs2() { // 手动创建RestTemplate并绑定Sleuth拦截器 RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add(sleuthInterceptor); return restTemplate.getForObject("http://ms2/api/xxx", String.class); } }
方式二:修复自定义拦截器(不推荐,优先用官方实现)
如果一定要自定义拦截器,必须正确创建子Span并传递链路头信息:
@Component public class SleuthTraceInterceptor implements ClientHttpRequestInterceptor { private final Tracer tracer; private final SpanNamer spanNamer; public SleuthTraceInterceptor(Tracer tracer, SpanNamer spanNamer) { this.tracer = tracer; this.spanNamer = spanNamer; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 1. 创建子Span,按规范命名为HTTP方法+请求URI String spanName = spanNamer.name(request.getMethod(), request.getURI()); Span span = tracer.nextSpan().name(spanName).start(); try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { // 2. 注入X-B3系列头信息,使用子Span的上下文 HttpHeaders headers = request.getHeaders(); headers.add("X-B3-TraceId", span.context().traceId()); headers.add("X-B3-SpanId", span.context().spanId()); if (span.context().parentId() != null) { headers.add("X-B3-ParentSpanId", span.context().parentId()); } headers.add("X-B3-Sampled", "1"); // 标记为采样链路 // 3. 执行HTTP请求 ClientHttpResponse response = execution.execute(request, body); // 4. 标记Span状态:根据响应码记录状态,错误时标记异常 span.tag("http.status_code", String.valueOf(response.getStatusCode().value())); if (response.getStatusCode().isError()) { span.error(new RuntimeException("HTTP调用失败,状态码:" + response.getStatusCode())); } return response; } finally { // 5. 结束当前子Span span.end(); } } }
之后在控制器中手动将自定义拦截器绑定到RestTemplate:
@RestController public class YourController { private final SleuthTraceInterceptor customInterceptor; public YourController(SleuthTraceInterceptor customInterceptor) { this.customInterceptor = customInterceptor; } @GetMapping("/call-ms2") public String callMs2() { RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add(customInterceptor); return restTemplate.getForObject("http://ms2/api/xxx", String.class); } }
关键注意事项
- 禁止复用当前Span:每次外部HTTP调用都要创建独立的子Span,这是链路追踪的核心规则,确保链路层级关系清晰可追溯。
- 必须手动绑定拦截器:手动
new的RestTemplate不会自动被Sleuth代理,必须显式将拦截器添加到其拦截器列表。 - 优先使用官方实现:官方拦截器已经处理了采样规则、错误标记、Span命名规范等边界情况,自定义容易遗漏细节导致链路异常。
内容的提问来源于stack exchange,提问作者New2Java
相关产品推荐
相关产品推荐

