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 /product、GET /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。这点得结合实际插件验证。