You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用new创建RestTemplate时无法传递Sleuth TraceId和SpanId至下游服务

问题分析与解决方案

你的自定义拦截器存在的核心问题

  1. 未创建子Span:Brave官方拦截器会为外部HTTP调用生成独立的子Span,将子Span的ID作为X-B3-SpanId传递,同时把当前服务的SpanID作为X-B3-ParentSpanId。你直接复用当前Span的ID作为下游的SpanId,会导致链路层级断裂,下游服务无法正确识别父Span关联关系,甚至生成新的TraceId。
  2. 可能存在拦截器未正确绑定的情况:如果仅注入了拦截器,但未手动将其添加到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:27:30