读书笔记 · 约 12,667 字 · 预计推送 2 部分

OpenTelemetry 入门:trace、metric、log 的统一观测模型 :: 设计个人 AI 应用埋点方案(精读笔记) 未开始

目录

题记与说明

条目前缀: knowledge-qyxuo8eu-2
精读日期: 2026-11-01
来源类型: 主题研究(官方文档为主 + 官方博客 + 厂商实操解读,共 8 个来源,全部网络实测可达)
阅读目的: 为个人 AI 应用设计一套 trace/metric/log 统一的埋点方案,先吃透 OTel 的统一观测模型

来源清单

#来源链接角色
S1Observability Primer(可观测性入门)https://opentelemetry.io/docs/concepts/observability-primer/ (原文核对: /docs/concepts/observability-primer/index.md)三信号的教科书式定义
S2Signals 概览页https://opentelemetry.io/docs/concepts/signals/OTel 对"信号"的总定义(2026-03-10 最后修改)
S3Traces 概念页https://opentelemetry.io/docs/concepts/signals/traces/span 解剖学:context/attribute/event/link/kind/status
S4Metrics 概念页https://opentelemetry.io/docs/concepts/signals/metrics/七种度量仪器、聚合、cardinality
S5Logs 概念页https://opentelemetry.io/docs/concepts/signals/logs/log record 字段、与 trace 的自动关联
S6What is OpenTelemetry?https://opentelemetry.io/docs/what-is-opentelemetry/OTel 是什么/不是什么、组件图景
S7官方博客《Inside the LLM Call: GenAI Observability with OpenTelemetry》(2026)https://opentelemetry.io/blog/2026/genai-observability/GenAI 语义约定的动机与生态采纳
S8Dash0: OpenTelemetry GenAI Semantic Conventions Explainedhttps://www.dash0.com/knowledge/opentelemetry-genai-semantic-conventions-explainedgen_ai.* 属性/指标/事件清单与稳定性陷阱
S9semantic-conventions-genai 仓库(GenAI 约定新家)https://github.com/open-telemetry/semantic-conventions-genai约定迁移证据:v1.42.0 起独立仓库开发
⚠️ 版本考古:S1 引用自 /docs/concepts/observability-primer/index.md 原文核对;旧路径 /docs/what-is-otel/ 已 404,现路径为 /docs/what-is-opentelemetry/(本次实测确认)。

七维提取

一、核心问题

分布式系统(以及如今的 LLM/Agent 应用)的行为对开发者是不可见的:请求经过多个组件,故障难以本地复现。经典三件套 trace/metric/log 长期以来各自为政——每种可观测性后端有自家的 SDK 和协议,开发者每换一家厂商就要重学一套 API;而日志孤零零地存在时"become far more useful"要等到挂上 trace 上下文之后(S1)。OTel 要解决的是用一套厂商中立的标准统一产生、处理、导出三类信号;2025–2026 的延伸问题是:LLM 调用和 Agent 执行是新的黑盒,官方博客开场就点出痛点——"Your AI agent just took 45 seconds to answer a simple question. Was it the model? A slow tool call? A retry loop?"(S7)。

二、主要主张

  1. OTel 是框架不是后端:"An observability framework and toolkit"——"OpenTelemetry is not an observability backend itself",存储与可视化"are intentionally left to other tools"(S6)。
  2. 三信号各司其职、可互相转换视角:signals 页的一句话总纲——"The purpose of OpenTelemetry is to collect, process, and export signals. Signals are system outputs that describe the underlying activity of the operating system and applications running on a platform."(S2)。五个信号的一句话定义(S2 原文):Traces "The path of a request through your application";Metrics "A measurement captured at runtime";Logs "A recording of an event";Baggage "Contextual information that is passed between signals";Profiles "A recording of resource usage at the code level"。
  3. 统一 API + 统一协议消除锁定:两条指导原则——"You own the data that you generate. There's no vendor lock-in" 和 "You only have to learn a single set of APIs and conventions"(S6)。
  4. GenAI 调用可以纳入同一模型:LLM call 不再是黑盒,而是一棵 span 树(invoke_agent → chat / execute_tool)+ 两个指标(gen_ai.client.operation.duration、gen_ai.client.token.usage)+ 可选内容事件(S7、S8)。
  5. 隐私默认关闭:"By default, no prompt content or tool arguments are captured with GenAI telemetry, as these can contain sensitive data."(S7)。

三、方法(技术路线)

分层架构(S6):Specification → OTLP 协议("A standard [protocol] that defines the shape of telemetry data")→ 语义约定(Semantic Conventions)→ 各语言 API → SDK → instrumentation 库生态 → 零代码插桩("generate telemetry data without requiring code changes")→ Collector("a proxy that receives, processes, and exports telemetry data")。历史:CNCF 项目,由 OpenTracing 与 OpenCensus 合并而来——两者都曾独自尝试标准化插桩而失败(S6)。

三信号的内部机制(S3/S4/S5):

GenAI 扩展(S7/S8/S9):给 LLM 调用定义了同样三件套——span 命名规则 {gen_ai.operation.name} {gen_ai.request.model}(如 chat gpt-4),操作词表覆盖 chat / generate_content / text_completion / embeddings / create_agent / invoke_agent / invoke_workflow / plan / execute_tool / retrieval / create_memory / search_memory / upsert_memory;指标族含 client 与 server 两侧;内容不进 span 属性而是进 opt-in 事件 gen_ai.client.inference.operation.details(携带 gen_ai.input.messages / gen_ai.output.messages),理由是内容"routinely contain personal data"且 span 属性"indexed, size-limited, and a poor place to put it"(S8)。

四、数据与证据

  1. 定义原文已逐条核对:S1 的核心定义取自官方 markdown 原文——"Observability lets you understand a system from the outside by letting you ask questions about that system without knowing its inner workings.";"Telemetry refers to data emitted from a system and its behavior.";"Reliability answers the question: 'Is the service doing what users expect it to be doing?'";"Metrics are aggregations over a period of time of numeric data about your infrastructure or application.";"A log is a timestamped message emitted by services or other components.";"A span represents a single unit of work or operation.";"A distributed trace, more commonly known as a trace, records the path taken by a single request... as it propagates through multiple services in an architecture."
  2. S2 原文由 webReader 直接取得(含 2026-03-10 修改记录),五信号一句话定义一字未改引用如上。
  3. 生态采纳证据(S7 官方博客):VS Code Copilot "emits traces, metrics, and events for every agent interaction"(开关 github.copilot.chat.otel.enabled);OpenAI Codex 导出结构化日志事件与 OTel 指标;Claude Code "exports metrics and log events via OTel, with trace support in beta"。演示链路为 Copilot → OTLP → 本机 Aspire Dashboard(http://localhost:4318),其 GenAI 可视化器"parses these attributes and renders a chat-style view of the conversation"。
  4. 指标价值的量化表述(S7):指标使团队能 "estimate per-request cost, catch token-hungry prompts before they hit production"。
  5. 稳定性与迁移的证据(S8):全部 gen_ai.* 处于 Development 稳定性("none of this is stable yet"),GenAI span 上仅 error.type、server.address、server.port 继承自核心约定而 Stable;v1.42.0(2026-06-12)起约定迁入独立仓库 semantic-conventions-genai(S9 实证:README 中 Schema URL 一节仍是 "TODO",尚无 tagged release);因无 schema URL,OTel 的 schema migration 对其不工作。
  6. 属性证据(S8 清单):每个推理 span 必填 gen_ai.operation.name、gen_ai.provider.name(取值如 openai、anthropic、aws.bedrock);条件必填 gen_ai.request.model、error.type、gen_ai.conversation.id、gen_ai.output.type;推荐 gen_ai.usage.input_tokens / gen_ai.usage.output_tokens、temperature、top_p、finish reasons、gen_ai.response.model;token 细分含 gen_ai.usage.cache_read.input_tokens、gen_ai.usage.reasoning.output_tokens;Agent/工具/检索/记忆属性含 gen_ai.agent.name / gen_ai.agent.id / gen_ai.tool.name / gen_ai.tool.call.id / gen_ai.data_source.id / gen_ai.retrieval.top_k / gen_ai.memory.record.count 等。指标含 gen_ai.client.token.usage、gen_ai.client.operation.duration、gen_ai.client.operation.time_to_first_chunk、gen_ai.invoke_agent.duration、gen_ai.invoke_agent.tool_calls、gen_ai.invoke_agent.inference_calls、gen_ai.execute_tool.duration 及 server 侧 gen_ai.server.*。
  7. 证据充分性:统一模型本身证据充分(官方规范+多语言 stable 状态:metrics API/SDK 在 C++/.NET/Go/Java/JS/PHP/Python/Rust 已 Stable,S4);GenAI 部分证据来自官方博客+厂商解读,一致但均未到 stable,Dash0 与官方博客在细节词表上已有分歧(见局限)。

五、结论与效果

  1. 统一观测模型成立:三信号不是并列的三种日志,而是一张互指的网——span 组成 trace;log record 内嵌 TraceId/SpanId 自动挂链;metric 用低基数维度聚合全局;Baggage 在信号之间传上下文(S2–S5)。开发者学一套 API,数据以 OTLP 传输,后端随时可换。
  2. 个人/小规模场景完全可负担:官方博客的演示就是一个本地 Docker 的 Aspire Dashboard 加 localhost:4318,不需要任何云端服务(S7);个人应用甚至可以跳过 Collector 直连后端(Collector 的定位是"proxy",接收/处理/导出,多源汇聚、敏感信息过滤、重试缓冲时才必要,S6)。
  3. AI 应用埋点已有标准答案:不需要自造 schema——gen_ai.* 语义约定给出 span 树、指标、事件的全套命名,主流编码助手已实装(S7);但约定还在快速演化,落地时要按"库层 pin 版本 + 薄映射层 + 采集端归一化"三招防御变化(S8 的三条 adoption advice:Pin at the library, not the spec;normalize at ingest via a Collector transform processor;keep gen_ai.* strings behind a thin mapping layer)。
  4. 实测路径佐证:本次精读中 9 个来源全部在线抓取成功(含一次 webReader 瞬时 500 后改用 WebFetch 成功),旧路径 /docs/what-is-otel/ 404 亦是本次实测发现。

六、局限

  1. GenAI 约定未 stable(作者方承认,S8/S9):Development 稳定性,属性可能改名、span 形状可能重构;无 schema URL 发布,官方 schema migration 机制对其失效;semantic-conventions-genai 仓库尚无 tagged release(S9 README 的 Schema URL 一节为 TODO)。
  2. 词表正在漂移:官方博客(S7)与较新的 Dash0 文章(S8)之间已出现分歧——旧词汇 gen_ai.system 在新约定中改为 gen_ai.provider.name;旧事件名 gen_ai.system.message / gen_ai.user.message / gen_ai.choice 被单一事件 gen_ai.client.inference.operation.details 取代。照抄任何一篇文章都可能踩到旧词表。
  3. 内容捕获的两难:默认不抓内容(隐私安全),但只看 token 数与延迟排查不了质量问题(如 prompt 本身的问题);开关 OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT 行为还"behaves differently across libraries"(S8),语义不统一。
  4. cardinality 溢出的静默代价(S4):溢出后整个属性集被替换——"a {url.path, success=false} measurement loses success too",按被丢弃属性过滤的查询会低估,且只有 Resource/instrumentation scope 属性豁免。
  5. 我推断的局限(非作者声明):官方文档面向分布式微服务场景,个人单机应用照搬 Collector+多后端会过度设计;文档对"本地采样策略该怎样选"着墨少,个人应用全量采集即可但文档未明说;博客演示用 Aspire Dashboard 偏 .NET 生态,非 .NET 用户的等价本地后端(Grafana LGTM、Langfuse、Jaeger)需自行拼装。

七、对我的启发(设计个人 AI 应用埋点方案)

设计目标:每次对话/Agent 运行能回答四个问题——花了多少钱、慢在哪一步、错在哪一步、当时模型看到了什么(按需开)。以下方案全部锚定上文来源,不含未经验证的组件。

1. 信号分工(统一模型的直接应用)

信号在个人 AI 应用里记什么依据
Trace一棵 span 树:invoke_agent(root)→ execute_tool {tool.name} / retrieval / chat gpt-4;跨进程异步任务用 Span Links 表达因果S3、S7、S8
Metricgen_ai.client.token.usage(按 gen_ai.token.type 分 input/output/cache_read/reasoning)、gen_ai.client.operation.duration、gen_ai.execute_tool.duration、gen_ai.invoke_agent.tool_calls;本地价格表 × token 直方图 = 每请求成本S4、S7、S8
Log既有日志库经 bridge 进入 OTel,log record 自动带 TraceId/SpanId 挂到 trace 上;body 用结构化 JSON("Structured logs imply a consistent schema or well-defined typed fields")S5
Baggagegen_ai.conversation.id 一类会话级上下文跨信号传递S2、S3

2. 最小架构(个人级,避免过度设计)

3. 实施纪律(全部有出处)

  1. 默认零内容捕获:只记 model、token 数、时长、finish reasons;排查质量问题时临时开 OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,看完关掉(S7 隐私原则 + S8 环境变量)。
  2. cardinality 纪律:metric 维度只放 gen_ai.request.model、gen_ai.provider.name、gen_ai.token.type、tool.name 这类低基数值;绝不放 prompt hash、message id、时间戳(S4:默认上限 2000,溢出会静默替换整个属性集)。
  3. gen_ai.* 字符串收口:在自己代码里建一个薄映射层(常量表),不把 gen_ai.provider.name 这类字符串散落各处——约定改名时只改一处(S8 advice 3)。
  4. pin 库版本而非 spec:约定无 schema URL、无 tagged release,升级 instrumentation 库前先看其 semconv 版本(S8 advice 1 + S9)。
  5. span 粒度对齐钱和延迟:每个 LLM 调用一个 chat span、每个工具一个 execute_tool span——45 秒慢回答的归因问题(模型慢 vs 工具慢 vs 重试循环)就是靠这棵树回答的(S7 开场问题)。

4. 一句话版方案:SDK 自动插桩打底 + 手动补 agent 树上的关键 span;metric 只记"钱和延迟"的低基数聚合;log 经 bridge 白拿 trace 关联;内容默认不抓、事件按需开;OTLP 直连本地看板;gen_ai.* 走映射层防漂移。


这次学到什么

  1. 三信号是一张网,不是三个桶:判断某个信息该埋进哪个信号,标准是"要回答什么问题"——单次请求的归因用 trace,跨时间的统计用 metric,事件本身的现场用 log;log+trace 的关联是 OTel 免费送的,别手写。
  2. "框架而非后端"的架构红利:因为数据归你(OTLP 标准),个人应用可以先用最便宜的后端起步,换后端只改 exporter 配置,埋点代码零改动。
  3. 新领域语义约定 = 现成 schema:给 AI 应用埋点不需要发明字段名,gen_ai.* 已把 agent/tool/retrieval/memory 全覆盖;但 Development 级约定的正确用法是"映射层隔离 + 库层锁版本",就像依赖一个快速迭代的 API 时不应把字段名硬编码进业务逻辑。
  4. cardinality 是埋点方案的第一性约束:能不能聚合、会不会溢出、查询会不会失真,都取决于"这个属性进了哪个信号、以什么基数存在"——设计先于编码。

*笔记完 · 精读员·#2 · 2026-11-01*

← 资料库今日播报 →