编程 trace 只到本服务、下游变新 root:用 traceparent 排查上下文传播断链

2026-09-21 00:03:14

trace 只到本服务、下游变新 root:用 traceparent 排查上下文传播断链

微服务里常见一种现象:A 调 B,Jaeger/Tempo 里只看到 A 的 span,B 是新的 root span,一条请求断成好几截。采样开关没动,B 的日志也证明请求到了下游。这类问题多半不是采样丢 span,而是上下文传播(context propagation)断了:A 没有把当前 trace 的上下文带给出站请求,B 拿不到 trace_id/span_id,只能自己起一条新链路。

OpenTelemetry 官方文档:Context propagation

Context 和 Propagation 是两回事

Context 是携带关联信息的对象。Service A 调 Service B 时,A 会把 trace ID、span ID 放进 context;B 用这些值创建同一条 trace 下的新 span,并把 A 的 span 设为 parent。这样跨服务边界才能拼出完整调用链。

Propagation 是把 context 在服务、进程之间搬运的机制,负责序列化/反序列化 context,把必要信息从一个服务传给另一个。它通常由插桩库处理,对使用者透明;需要手动传播时用 Propagators API。OpenTelemetry 维护了若干官方传播器,默认传播器使用 W3C TraceContext 规范指定的头。

traceparent 的实际格式

传播到 HTTP 上时,trace 上下文放在 traceparent 头里,格式为:

--
-

官方示例:

00-a0892f3577b34da6a3ce929d0e0e4736-f03067aa0ba902b7-01

逐字段看:

  • 00:version,当前 W3C TraceContext 版本。
  • a0892f3577b34da6a3ce929d0e0e4736:trace-id,32 位十六进制,整条链路共享。
  • f03067aa0ba902b7:parent-id,16 位十六进制,即上游 span ID,用来给下游 span 设 parent。
  • 01:trace-flags,采样位。01 表示 sampled,下游应按采样决定继续记录。链路断掉时这个头可能根本没出现,或者出现但没被 extract。

注入和提取两端:常见断链点

发送方做 inject:把 context 注入 carrier,例如 HTTP 请求头。接收方做 extract:从 carrier 里取回 context,再放进本地 context。断链通常发生在某一端没做,或者中间载体把信息吃掉了。

  • 新起 goroutine / 线程池丢本地 context:业务代码另开执行单元后,没有把当前 context 传进去。出站请求执行时拿不到父 span,自然注入不出 traceparent
  • 消息队列 / 异步任务只带业务 body:生产端只序列化了 payload,没有把 traceparent 放进 message header 或 attribute;消费端只反序列化 body,extract 不到任何 trace 信息。
  • 协议本身没有 metadata 字段:官方提醒,可以在没有专用 metadata 字段的协议里传播 context,但必须确保接收侧在处理数据前把它们提取并移除,否则可能产生未定义行为。
  • 第三方 HTTP client 没插桩:出站调用绕过了 OpenTelemetry 的 transport 包装,请求发出去了,但没有 inject。

手动传播时 API 的分工

需要自己支持传播时,用 Propagators API:

propagator.inject(context, carrier, setter)
propagator.extract(context, carrier, getter)

发送侧把 context 注入 carrier;HTTP 场景就是写 header。没有专用 metadata 字段的协议,需要自己找地方存。接收侧从 carrier 提取,位置要和发送侧一致。HTTP 场景从 header 取,其他协议从你选定的字段取。

传播不只有 trace

logs 也会被注入 TraceID/SpanID,实现 trace-log 关联。这样既能看到某条日志属于哪个 trace/span,也能跨服务边界把同一链路的日志归到一起。

metrics 能按 context 聚合。官方给的例子是:不只看所有 GET /product 的响应时间,还能得到 POST /cart/add > GET /productGET /checkout > GET /product 这类组合指标。断链后,trace 碎了,关联日志和组合指标也会跟着失真。

安全:入站 header 不可信

传播要跨服务边界收发数据,有安全影响。

  • 入站 context:不要默认信任外部来源。恶意方可能伪造 trace header,污染追踪数据,或触发 context 解析漏洞。对不受信来源的入站 context,可以忽略或清洗。
  • 出站 context:内部 trace ID、span ID、baggage 项可能暴露内部架构或业务逻辑。可以配置传播器,不向外部或公网端点发送 context。
  • Baggage:baggage 允许传播任意键值对,会随链路发给下游,不要放用户凭证、API Key、PII,以免被记录或发送到不受信服务。

如果下游是消息队列消费者,需要按框架确认 extract 发生在哪一步:有的把 header 透传到 message attributes,有的只留 body。这点得结合实际插件验证。

参考

推荐文章

程序员茄子在线接单