生产部署清单
k8s / docker / host 探测,自动补 pod 名/节点名attributes / transform 处理 删除邮箱/手机/身份证file exporter 做失败回写/healthz / /ready / /metrics 这类轮询路径,用 ignoreIncomingRequestHook 丢弃,否则 span 量暴涨采样策略总纲
入口采样率 = ParentBased(TraceIdRatio(1.0)) # 应用全采,交给 Collector 决定
↓
Agent 层 = 透传(batch + memory_limiter)
↓
Gateway 层 = Tail Sampling
- status=ERROR → 100% 保
- latency > 500ms → 100% 保
- tenant 是 VIP → 100% 保
- 其他 → 1-5%
应用不做采样的好处:业务代码不依赖采样策略,换策略只改 Collector。
高基数的血泪教训
某团队把
user_id 放进了 http_requests_total 的 label。1000 万用户 + 10 个路由 = 1 亿条时间序列。Prometheus 内存从 10GB 涨到 200GB,crashloop 了一天。修复:Collector 层用
attributes processor 把 user_id 从 metric 路径上删除(保留在 trace);重启 Prom,压缩旧数据,恢复。
一般规则:metric 的 attribute 基数 < 100。常见雷区:
- user.id / session.id / request.id → 用 trace 记录,不进 metric
- url.path(未归一化)→ 用 http.route 替代
- SQL 原文 → 用参数化语句 + 操作名
- 异常 stack 哈希 → 用 error.type 分组
Collector 层统一治理
processors:
attributes/clean:
actions:
- key: user.id
from_context: span # 从 span 移除(别污染 metric)
action: delete
- key: password
action: delete
- key: email
action: hash # 哈希后保留
transform/metric_safety:
metric_statements:
- 'keep_matching_keys(attributes, "http\\..*|rpc\\..*|service\\..*")'
# metric 只保留协议字段,业务字段全丢
把治理规则沉淀到 Collector——运维一份配置,整公司自动合规。
LLM 可观测:GenAI SemConv
2024 年末 OTel 加了 GenAI 工作组,2025 Q3 一批字段转 Stable。核心思路:LLM 的每一次调用就是一个 span,attributes 带上模型、token、延迟、成本。
OpenAI 调用埋点
import { trace, SpanStatusCode } from "@opentelemetry/api"; const tracer = trace.getTracer("llm-app"); async function callOpenAI(messages: Message[]) { return tracer.startActiveSpan("chat gpt-4o", async (span) => { span.setAttributes({ "gen_ai.system": "openai", "gen_ai.request.model": "gpt-4o", "gen_ai.operation.name": "chat", "gen_ai.request.temperature": 0.7, "gen_ai.request.max_tokens": 2000, }); try { const res = await openai.chat.completions.create({ model: "gpt-4o", messages, temperature: 0.7, }); span.setAttributes({ "gen_ai.response.model": res.model, "gen_ai.usage.input_tokens": res.usage!.prompt_tokens, "gen_ai.usage.output_tokens": res.usage!.completion_tokens, "gen_ai.response.finish_reasons": [res.choices[0].finish_reason], }); span.setStatus({ code: SpanStatusCode.OK }); return res; } catch (e) { span.recordException(e as Error); span.setStatus({ code: SpanStatusCode.ERROR }); throw e; } finally { span.end(); } }); }
成本计算:把 token 变成 $
# Collector 里用 OTTL 算成本 processors: transform/cost: trace_statements: - 'set(attributes["gen_ai.cost.input_usd"], attributes["gen_ai.usage.input_tokens"] * 0.0025 / 1000) where attributes["gen_ai.request.model"] == "gpt-4o"' - 'set(attributes["gen_ai.cost.output_usd"], attributes["gen_ai.usage.output_tokens"] * 0.01 / 1000) where attributes["gen_ai.request.model"] == "gpt-4o"'
每次 LLM 调用都附带成本字段——Grafana 上按 tenant/user/feature 聚合 = 实时成本看板。
LangChain / LlamaIndex 追踪
主流 LLM 框架都接了 OTel(有些叫 callbacks,有些直接 OTel 原生):
# LangChain from opentelemetry.instrumentation.langchain import LangchainInstrumentor LangchainInstrumentor().instrument() # 之后任何 LangChain chain 自动产 span chain = prompt | llm | parser result = await chain.ainvoke({"q": "hello"}) # Jaeger 上看到:chain → prompt template → LLM call → parser 的完整链路
# LlamaIndex from opentelemetry.instrumentation.llama_index import LlamaIndexInstrumentor LlamaIndexInstrumentor().instrument()
OpenLLMetry:社区增强
OpenLLMetry 是 Traceloop 维护的 OTel 扩展,在标准 GenAI SemConv 之上:
- 自动埋点 30+ LLM 库(OpenAI/Anthropic/Cohere/Bedrock/LangChain/LlamaIndex/CrewAI/AutoGen)
- 记录 prompt 和 completion 原文(可选,PII 注意)
- 和 GenAI SemConv 完全兼容
from traceloop.sdk import Traceloop Traceloop.init(app_name="my-llm-app") # 下面所有 LLM 调用自动追踪,发到任何支持 OTLP 的后端
Agent / Tool 链路
LLM agent 的多步 tool 调用最适合 trace 可视化:
research-agent (trace_id=abc) ├─ LLM call (decide tool) gpt-4o, 200ms, in=1200/out=45 tok ├─ tool: web_search Brave API, 800ms ├─ LLM call (analyze result) gpt-4o, 600ms, in=3800/out=312 tok ├─ tool: calculator local, 5ms ├─ LLM call (final answer) gpt-4o, 400ms, in=4200/out=200 tok └─ total: 3.5s, $0.018, 5 spans
Jaeger 瀑布图里每个 tool 是一个 span,你能看到:哪步最慢、哪个模型用 token 最多、哪个 tool 失败重试——LLM 应用优化的第一手资料。
RAG 链路
RAG query span ├─ embed user query (OpenAI text-embedding-3-small, 80ms) ├─ vector search (Qdrant, 15ms, top_k=20) ├─ rerank (Cohere rerank, 120ms, top_n=5) ├─ LLM generation (gpt-4o, 1200ms, ctx 3200 tok) └─ total 1.4s
每一步都是 span,attributes 带 db.vector.dimensions / db.vector.top_k 等。这些是 RAG 优化(embedding 模型、top_k 调参)的依据。
LLM 专属 metric
const tokens = meter.createHistogram("gen_ai.client.token.usage", { unit: "token", }); const cost = meter.createCounter("gen_ai.client.cost_usd", { unit: "USD", }); // 在每次调用后 tokens.record(usage.prompt_tokens, { "gen_ai.request.model": model, "gen_ai.token.type": "input", }); tokens.record(usage.completion_tokens, { "gen_ai.request.model": model, "gen_ai.token.type": "output", }); cost.add(computedUsd, { "gen_ai.request.model": model, "feature": "summarize", });
每日成本看板、按 feature 聚合 token 消耗、分位数延迟——全靠这几个 metric。
Evals 接入
评估结果也可以走 OTel:
span.setAttributes({ "gen_ai.evaluation.name": "faithfulness", "gen_ai.evaluation.score": 0.87, "gen_ai.evaluation.passed": true, });
在 Langfuse / Phoenix 这类 LLM 观测平台上,这些字段被渲染成专属的 eval 面板。
9 个常见 troubleshooting
OTEL_LOG_LEVEL=debug 看 SDK 日志;span.end() 忘了?exporter endpoint 正确?Collector 收到没(debug exporter 打开)?send_batch_max_sizememory_limiter 一定要配;SDK 层 BatchSpanProcessor 队列不要设太大attributesprocessor 删除嫌疑字段;Prom 侧 scrape_interval 放大应急underscores_in_headers 没开?Envoy 把 traceparent 过滤了?文档 & 告警规范
· 埋了哪些 span / metric / log,含义
· 用的是自动还是手动 instrumentation
· 业务 attribute(acme.*)列表
· 对应的 Grafana 面板链接
· 关键 SLO + 告警门槛(p99 / 错误率 / 成本)
这是从"埋了点"到"可运维"的关键文档。
最终回顾
- 概念:可观测性 ≠ 监控;三支柱 Logs/Metrics/Traces 靠 trace_id 串联
- API:Tracer/Meter/Logger + Context propagation(W3C TraceContext)
- SDK:自动 instrumentation 搞定 80% 通用场景,业务节点手动补
- Collector:管道模型 receiver→processor→exporter;Agent+Gateway 两层
- 语义约定:标准命名是预置面板、跨后端的钥匙
- 后端选型:Grafana LGTM(开源)/ Datadog(全能)/ Honeycomb(高基数)
- 生产:tail sampling、高基数治理、PII 脱敏、Collector 自监控
- LLM:GenAI SemConv Stable,每次模型调用/tool/agent 都是 span
OTel 不是银弹,但它是把「可观测性」从厂商专有变成行业基础设施的那次关键标准化——就像 HTTP 之于 Web、Docker 之于容器、K8s 之于编排。埋一次、处处能用,这个承诺兑现了。
本教程到此结束。祝你的系统永远在绿色仪表盘上,出问题的那刻一键回滚。