Traces 与 OpenTelemetry
Trace、Span、Context Propagation、采样、Collector、Tempo,以及Planner、Model、Tool与Retrieval的Agent Span设计。
目录 · 21 节
- 1. Trace和Span
- 2. Parent、Event与Link的选择
- 3. Context Propagation
- Baggage的使用边界
- 4. OpenTelemetry的职责
- Collector的价值
- 5. 采样
- Head Sampling
- Tail Sampling
- 6. Tempo的职责
- 7. Metrics、Trace与Exemplar
- 8. Agent Span设计
- 9. Trace Recorder和OTel Trace的关系
- 10. Trace常见故障模式
- 11. Tracing测试
- 12. 高频追问
- Trace和日志有什么区别?
- 为什么不能所有Span都100%保存?
- OpenTelemetry和Tempo是什么关系?
- 多Agent之间如何串Trace?
- 参考资料
Traces 与 OpenTelemetry
Trace描述一次请求或任务穿过系统的完整路径,Span描述其中一个具有起止时间的操作。Metrics显示checkout-api P99=2.4s时,Trace可以进一步确定耗时来自鉴权、队列、模型、数据库还是某个Tool调用。
1. Trace和Span
Trace: checkout request
└─ Span: HTTP POST /orders 2.4s
├─ Span: auth.verify 0.1s
├─ Span: cart.load 0.2s
├─ Span: db.query 1.8s
└─ Span: payment.reserve 0.1s
一个Span通常包含:
| 字段 | 含义 |
|---|---|
| Trace ID | 标识整条链路 |
| Span ID | 标识当前操作 |
| Parent Span ID | 表示同步调用或层级关系 |
| Name / Kind | 操作名称和Client、Server、Producer等角色 |
| Start / End | 开始、结束和Duration |
| Attributes | 可查询的结构化属性 |
| Status | Unset、OK或Error语义 |
| Events | Span内部某个时刻发生的事件 |
| Links | 与另一个Trace或Span的非父子关系 |
| Resource | service.name、environment、版本等进程身份 |
不应将动态信息全部写入Span Name。tool/loki/query?tenant=acme&...会产生无界名称;应使用稳定名称tool.call,并将tool.name=loki记录为有界属性。
2. Parent、Event与Link的选择
- Child Span:当前操作属于父操作的执行树,例如HTTP Handler调用数据库;
- Span Event:Span内部一个瞬时事件,例如Retry、Exception或模型产生一个Action;
- Span Link:存在因果或批处理关系,但不适合单一父子结构,例如Kafka消费者处理批次、任务续跑或Fan-in。
异步队列不应机械保留一个持续数小时的父Span。生产者注入Context,消息携带Trace信息;消费者提取后创建消费Span,并根据语义选择Parent或Link。设计目标是准确表达因果关系,而不是强制所有操作组成单一父子树。
3. Context Propagation
跨进程Trace成立的前提,是上下文随请求传播:
HTTP Header / gRPC Metadata / Message Header
→ trace_id + parent span + sampling decision
→ 下游提取
→ 创建Child Span
W3C Trace Context常见Header:
traceparent: 00-<trace-id>-<parent-id>-<flags>
tracestate: vendor-specific state
传播链中任意一跳忘记Inject或Extract,Trace都会断成两截。常见断点:线程池、异步任务、消息队列、自建HTTP Client、定时任务和跨语言SDK。
Baggage的使用边界
Baggage是随Context传播的键值信息,可以帮助跨信号关联,但它可能跨越多个服务和信任边界:
- 不放Token、密码、PII;
- 控制大小和键数量;
- 明确允许传播的字段;
- Baggage不会自动变成Span Attribute,需要显式映射;
- 不传播未经校验和授权的用户输入值。
4. OpenTelemetry的职责
OpenTelemetry提供Vendor-neutral的API、SDK、Semantic Conventions、OTLP协议和Collector生态,用于产生、处理和导出Traces、Metrics与Logs。
业务代码
→ OTel API / 自动或手动Instrumentation
→ SDK:Sampler、Processor、Exporter
→ OTLP
→ Collector:Receive、Process、Export
→ Tempo / Prometheus / Loki或其他后端
OpenTelemetry本身不是存储和查询后端。部署OTel SDK后,仍需配置Collector、后端存储、Data Source和Dashboard。
Collector的价值
Collector把应用与后端解耦,可以统一:
- Batch与Queue;
- Retry和Backpressure;
- Resource Detection;
- 属性增删、脱敏和过滤;
- Tail Sampling;
- 多后端导出;
- 多租户路由和限流。
Sidecar、DaemonSet或Gateway各有权衡。Gateway集中治理方便,但会成为共享容量和故障域;Agent或DaemonSet降低应用直连压力,但节点资源和配置管理更复杂。
5. 采样
全量Trace成本可能过高,需要Sampling。
Head Sampling
在Trace开始时决定是否采样:
- 延迟低、实现简单;
- 不能预先知道最终是否错误或是否成为慢请求;
- 低概率异常可能未被采样保留。
Tail Sampling
Collector等待Trace的足够多Span后再决定:
- 可以保留错误、慢请求、关键服务和特定属性;
- 需要缓存未完成Trace,增加内存和决策延迟;
- Trace跨多个Collector时,需要保证同一Trace进入一致的决策点;
decision_wait过短会得到残缺Trace,过长会增加资源占用。
一种策略:错误和高延迟100%保留,关键租户或新版本提高比例,其余正常请求概率采样。但必须让采样策略本身可观察:接收多少Trace、因何保留、因何丢弃、是否内存不足。
6. Tempo的职责
Tempo是Trace后端,用于接收、存储和查询Trace;Grafana可以通过Tempo Data Source展示Waterfall,并配置Trace to Logs、Trace to Metrics和Service Graph。
Trace通常以对象存储承载大量Span数据,查询依赖Trace ID、属性索引或派生结构。不要期待它像Prometheus一样对任意维度做超低延迟聚合;聚合趋势应产生Metrics,具体调用链留给Trace。
7. Metrics、Trace与Exemplar
Exemplar是在Histogram Bucket等聚合样本旁附带的代表性Trace引用:
P99曲线在10:20突然升高
→ 点击异常点的Exemplar
→ 打开一个属于该Bucket的Trace
→ 找到db.query慢Span
Exemplar支持从“一分钟内整体延迟升高”的聚合信号定位到一个具体慢请求。Exemplar不包含全部请求,也不能保证单个样本代表完整分布;它用于关联聚合指标与具体Trace。
8. Agent Span设计
一次Agent任务可建成:
agent.run
├─ planner.step
│ └─ model.generate
├─ agent.delegate metrics-agent
│ └─ tool.call prometheus.query
├─ agent.delegate log-agent
│ └─ tool.call loki.query
├─ retrieval.search
│ ├─ milvus.search
│ └─ neo4j.query
└─ synthesizer.generate
建议属性:
| Span | 属性 |
|---|---|
agent.run | run_id、workflow、tenant、stop_reason、success |
planner.step | step_index、selected_action、budget_remaining |
model.generate | provider、model、input/output token、cache hit |
tool.call | tool.name、status、retry、approval、result size |
retrieval.search | index.version、top_k、filter、candidate count |
synthesizer.generate | evidence count、citation count、schema valid |
以下内容要谨慎:
- Prompt和Completion可能含PII与Secret;
- Tool参数可能含URL、SQL或用户数据;
- Observation可能极大;
- 模型的隐藏思维链不应写入Trace;
run_id可作为Trace属性便于查询,但不要复制成Prometheus Label。
记录版本、计数、Hash、Evidence ID和错误分类,原始大对象存受控Evidence Store,Trace里保存引用。
9. Trace Recorder和OTel Trace的关系
二者重叠但不完全相同:
| OTel Trace | Agent Trace Recorder |
|---|---|
| 关注调用链、耗时和系统诊断 | 关注决策、证据、状态和可重放性 |
| 可能采样与按期删除 | 关键任务可能要求完整、持久化 |
| Span/Attribute/Event模型 | Domain Event、State Snapshot、Evidence引用 |
| 主要服务于运维排障 | 还服务于审计、评测和回归 |
推荐共用trace_id,但分开保留策略:
OTel Span → Tempo:链路与性能
Agent Event → SQL / Object Store:决策与证据
共同字段 → run_id / trace_id / evidence_id
这样既能在Grafana看链路,又能从失败Trace安全构造评测Case,不必要求Tempo永久保存所有Prompt和原始Tool结果。
10. Trace常见故障模式
- Span状态总是OK,即使操作返回业务错误;
- Span在异步操作完成前提前结束;
- 每个Span复制完整Prompt,导致存储成本和隐私风险显著增加;
- Context跨队列丢失;
- 时间不同步造成负Duration或瀑布错位;
- Tail Sampler没看到完整Trace;
- 服务名随Pod变化,Service Map像细胞分裂;
- 把异常事件记录了,却忘记设置错误状态和错误类型;
- Retry各自产生Span,但没有关联Attempt和最终结果。
11. Tracing测试
- 使用In-memory Exporter断言Span树、属性和状态;
- 契约测试HTTP、gRPC、Kafka的Context Inject/Extract;
- 模拟错误、取消、超时和Retry;
- 验证敏感属性被Processor删除;
- 检查Trace to Logs查询能命中对应日志;
- 压测SDK与Collector开销;
- 故障注入Collector、Exporter和后端;
- 验证采样策略始终保留关键安全和错误Trace。
12. 高频追问
Trace和日志有什么区别?
Trace提供一次请求跨组件的结构与耗时;日志记录某个时刻的具体事件。Trace用于定位耗时步骤,日志通常用于解释该步骤缓慢或失败的原因。
为什么不能所有Span都100%保存?
高流量系统的存储、网络和查询成本可能不可接受。应结合业务风险做Head/Tail Sampling,并完整保留错误、慢请求和关键操作。
OpenTelemetry和Tempo是什么关系?
OpenTelemetry负责产生、传播、处理和导出Telemetry;Tempo负责存储和查询Trace。它们分别处在采集标准和后端存储层。
多Agent之间如何串Trace?
委派消息传播Trace Context,每个Agent创建自己的Span;同步委派用父子关系,异步、续跑或Fan-in可使用Span Link,同时记录角色、任务ID和Handoff版本。