订单服务需要把租户等级传给库存服务;另一个聚合任务从两个独立输入产生结果。前者是随请求走的业务上下文,后者是一次 Span 与多个输入 Span 的因果关联。不能把这两件事都塞进 parent ID:一个 Span 只有一个父亲,但可以关联多个 Link;Baggage 即便跨服务存在,也不会自动变成导出 Span 的属性。

区分两条传播路径

对象 作用 实验输入 验证出口
Baggage Context 携带键值,显式注入 baggage 头 白名单 tenant_tier=gold inventory 响应 tier=gold
Span Link 新 Span 与既有 SpanContext 的非父子关系 两个不同 Trace 的输入 聚合 SpanData 两条 Link
Span attribute 新 Span 自身记录的属性 本实验不设置 聚合 SpanData Attributes 为空

订单 HTTP handler 调用注入,库存 handler 在自己的线程提取;Lab07Test 的 Link 实验则完全发生在单 JVM 中,不能把它说成跨进程网络导出。

源码入口与失败分支

固定 Java SDK SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020:Baggage.fromContext 取 Context 中的 Baggage,缺失时返回空值。W3CBaggagePropagator.inject 在调用它的线程序列化 Baggage 并经 setter 写入 carrier;为空则不写头。extract 在服务端 handler 线程读取头,头缺失或为空则返回原 Context,解析抛运行时异常也保留原 Context。上游 重复键测试 和 非法键测试 提供不同输入边界。

SdkSpanBuilder.addLink 丢弃无效 SpanContext;内部 addLink(LinkData) 达到链接数量上限便不再添加。上游 addLink_invalid 检查无效 Link 没有进入 SpanData。这些是源码及上游测试事实;下述实验只运行白名单、空头和无效 Link 等指定路径,没有测满配额时的丢弃数量。

教学实验:白名单与多输入

HttpTraceServices 是教学实现:在 order 的 client Scope 内创建只含 tenant_tier 的 Baggage,并注入 HTTP request;inventory 在 Trace Context 提取后再提取 Baggage。Lab06Test 的两进程 HTTP 断言检查响应 tier=gold、secret=null,并继续校验 Trace ID、parent ID 和无效 traceparent。测试没有把任何敏感值写入 HTTP 请求。另在本地 Lab07Test 中先构造包含 secret=private 的 Context,再明确筛出允许的字段;断言被注入的头恰为 tenant_tier=gold,提取后读取不到 secret。

聚合实验先建立两个不同 Trace 的输入 Span,用 addLink(first.getSpanContext()) 与 addLink(second.getSpanContext()) 构造聚合 Span,再加一个无效 SpanContext 作反例。完成后通过内存 exporter 取 SpanData:恰有两条 Link,聚合 Span 的 attributes 为空。Link 不修改聚合 Span 的 parent,也不会替代请求入口的显式上下文传播。

在 examples/opentelemetry-java/ 执行 JAVA_HOME=/tmp/otel-20260930/jdk-extract/usr/lib/jvm/java-21-openjdk-amd64 ./mvnw -q -pl sdk-labs -Dtest=Lab06Test,Lab07Test test;本次真实输出见 evidence/07/RUN.md。这里的内存 exporter 没有将 Span 发送到 Collector,HTTP 响应也不是 OTLP 接收证明。

容易混淆的安全与语义边界

“Baggage 会自动作为 Span 属性上传”不成立:本次 Attributes 为空,业务若显式复制则须另作基数和隐私评估。反过来,“没有进入 Span 属性就不会泄漏”也不成立:Baggage header 走网络,必须在信任边界前使用白名单,不能把个人信息、密钥或租户机密不加筛选地放进去。“两个输入任选其一作为 parent 即已表达合并关系”也不成立:另一个输入缺少对应 Link 就无法从聚合 Span 看见。

练习一。 将 order 的 BAGGAGE_PROPAGATOR.inject 改为注入包含 secret 的 Context,重跑双进程测试。说明哪个白名单断言失败,并说明仅查看聚合 Span 的 attributes 为什么发现不了泄漏。

练习二。 移除 addLink(second.getSpanContext()) 并重跑 Lab07Test;解释 Link 数量与不同 Trace 的检查分别意味着什么。再以无效 SpanContext 替换第一条 Link,确认错误输入不会成为有效关联。

导航与参考资料

05 线程池传播 · 06 HTTP 传播 · 当前篇:07 Baggage 与 Span Link。下一篇:08 头部采样(待写)。

参考资料:OpenTelemetry Java SDK 固定 SHA 的 Baggage、W3CBaggagePropagator、SdkSpanBuilder 及对应上游测试(上文固定链接);本地实验 examples/opentelemetry-java/evidence/07/RUN.md。