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

Play框架中OpenTracing分布式追踪上下文传播与生命周期管理

实现OpenTracing分布式追踪全局可用的实践方案

我来给你梳理一套落地性强的实现思路,核心是靠请求拦截/中间件把追踪Span绑定到整个请求生命周期里,既能解析上游传递的头信息,又能让业务代码随时拿到trace-id打日志、传给下游,最后还能自动清理。

核心逻辑拆解

不管你用什么后端框架,这套逻辑都通用:

  • 请求入口解析头:从HTTP请求头里提取OpenTracing标准的追踪字段(比如X-B3-TraceId、X-B3-SpanId,或者Jaeger的uber-trace-id),用Tracer创建对应的SpanContext,再启动当前请求的Span。
  • 绑定到请求上下文:把Span存到和当前请求强绑定的上下文容器里(Java用ThreadLocal/AsyncLocal,Node.js用AsyncLocalStorage,Python用contextvars),这样控制器、服务层、工具类都能直接拿。
  • 日志自动带trace-id:把trace-id放到日志的MDC(映射诊断上下文)里,日志配置里直接引用,不用业务代码每次手动传。
  • 下游请求自动传追踪头:发起下游HTTP请求时,从上下文取出Span,把追踪头注入到请求头里,无缝传给下游服务。
  • 请求收尾清理:响应返回后,Finish Span,同时清理上下文和MDC里的信息,避免线程复用导致的串数据问题。

以Spring Boot为例的具体实现

1. 写个拦截器处理Span的创建与清理

先整个拦截器,在请求进来时创建Span并绑定,请求结束时收尾:

@Component
public class OpenTracingInterceptor implements HandlerInterceptor {
    private final Tracer tracer;

    // 构造注入Tracer(比如JaegerTracer)
    public OpenTracingInterceptor(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 从请求头提取上游的追踪上下文
        SpanContext parentContext = tracer.extract(
                Format.Builtin.HTTP_HEADERS,
                new HttpServletRequestExtractAdapter(request)
        );

        // 创建当前请求的Span,把上游上下文作为父Span
        Span currentSpan = tracer.buildSpan(request.getRequestURI())
                .asChildOf(parentContext)
                .withTag(Tags.HTTP_METHOD.getKey(), request.getMethod())
                .withTag(Tags.HTTP_URL.getKey(), request.getRequestURL().toString())
                .start();

        // 把Span绑定到当前线程,方便后续获取
        tracer.activateSpan(currentSpan);
        // 同时把trace-id放到MDC,日志直接用
        MDC.put("trace-id", currentSpan.context().toTraceId());

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        Span activeSpan = tracer.activeSpan();
        if (activeSpan != null) {
            // 如果请求出错,给Span打错误标签
            if (ex != null || response.getStatus() >= 400) {
                activeSpan.setTag(Tags.ERROR, true);
                if (ex != null) {
                    activeSpan.log(Map.of("event", "error", "error.stack", ex.getMessage()));
                }
            }
            // 结束Span
            activeSpan.finish();
            // 关闭Scope,清理线程绑定
            tracer.scopeManager().active().close();
            // 清理MDC里的trace-id
            MDC.remove("trace-id");
        }
    }
}

然后注册这个拦截器到Spring MVC:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    private final OpenTracingInterceptor tracingInterceptor;

    public WebMvcConfig(OpenTracingInterceptor tracingInterceptor) {
        this.tracingInterceptor = tracingInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 对所有请求生效
        registry.addInterceptor(tracingInterceptor).addPathPatterns("/**");
    }
}

2. 日志配置自动带上trace-id

比如用Logback的话,修改logback-spring.xml的日志格式,加上%X{trace-id}:

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %X{trace-id}%n</pattern>
    </encoder>
</appender>

这样业务代码打日志的时候,不用手动传trace-id,日志里自动就带上了。

3. 下游请求自动传递追踪头

用RestTemplate调用下游服务时,把当前Span的追踪头注入到请求头里:

@Service
public class DownstreamService {
    private final RestTemplate restTemplate;
    private final Tracer tracer;

    public DownstreamService(RestTemplate restTemplate, Tracer tracer) {
        this.restTemplate = restTemplate;
        this.tracer = tracer;
    }

    public String callOtherService(String url) {
        HttpHeaders headers = new HttpHeaders();
        // 把当前Span的追踪信息注入到请求头
        tracer.inject(
                tracer.activeSpan().context(),
                Format.Builtin.HTTP_HEADERS,
                new HttpHeadersInjectAdapter(headers)
        );
        HttpEntity<Void> entity = new HttpEntity<>(headers);
        return restTemplate.exchange(url, HttpMethod.GET, entity, String.class).getBody();
    }
}

踩过的坑要注意

  • 异步处理的Span传递:如果用了线程池做异步任务,ThreadLocal里的Span会丢失。这时候可以用Spring的TaskDecorator把Span传递到异步线程里,或者在异步任务里手动重新激活Span。
  • 上下文一定要清理:如果请求结束后没清理Span和MDC,线程池复用的时候会把上个请求的trace-id带到新请求里,日志就串了。
  • 兼容不同追踪系统的头格式:比如Zipkin用X-B3-*系列头,Jaeger用uber-trace-id,要确保你的Tracer配置了正确的格式,不然解析和注入都会出错。

内容的提问来源于stack exchange,提问作者Thayne

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:37:16