关于 otel-instrumentation
面向使用 OpenTelemetry 和 Honeycomb 的开发者,介绍 SDK 配置、自定义追踪和采样等遥测实践,并说明如何为排查问题添加属性与异常事件。
主要功能
- 01配置 OTLP 端点与协议
- 02为现有 Span 添加属性
- 03创建自定义 Span 与关联
- 04记录异常日志事件
- 05选择 Span 与采样策略
适用场景
- 01将 OpenTelemetry 数据发送到 Honeycomb
- 02为关键业务操作增加追踪信息
- 03按属性调查性能异常
- 04关联异常日志与追踪
MCP 相关
文档提及可参考 MCP 调查流程,但未说明此技能提供或连接 MCP 服务。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ honeycombio/ agent-skill --skill otel-instrumentation安装后在 Claude Code 中输入/otel-instrumentation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/architectural-patterns.mdcollector-config.mdcustom-instrumentation.mdlambda.mdlocal-collector-debug-test.mdpython.mdsdk-setup-by-language.mdwide-event-attributes.md详细介绍
Honeycomb 的 OpenTelemetry 插桩
SDK 配置、自定义 Span、属性、Span 事件、采样和分层遥测。 有关概念基础(为何宽事件很重要,以及属性如何帮助调查问题),请参阅 observability-fundamentals 技能。
OTLP 配置和 SDK 设置
每个 OTel SDK 都需要这些环境变量才能向 Honeycomb 发送数据:
必需的环境变量
基本配置:
OTEL_SERVICE_NAME=your-service-name
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=YOUR_API_KEY"
可选但推荐:
# Protocol selection (default: http/protobuf)
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf # or grpc
# Signal-specific endpoints (override base endpoint for specific signals)
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.honeycomb.io/v1/traces
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://api.honeycomb.io/v1/metrics
指标(首选): 使用现代 OTLP 指标和原生数据点。使用数据集提示确认目标类型(metrics 或 events)。使用以下内容进行身份验证:
OTEL_EXPORTER_OTLP_METRICS_HEADERS="x-honeycomb-team=YOUR_API_KEY"
协议选择
OTEL_EXPORTER_OTLP_PROTOCOL 用于确定线路格式和传输方式:
http/protobuf(默认,推荐)——使用 protobuf 编码的 HTTPgrpc——使用 protobuf 编码的 gRPChttp/json——使用 JSON 编码的 HTTP(负载更大,速度更慢)
除非有特定的基础设施要求使用 gRPC,否则请使用 http/protobuf。
特定信号的端点
默认情况下,OTel SDK 会在 OTEL_EXPORTER_OTLP_ENDPOINT 后附加 /v1/traces 和 /v1/metrics。
使用特定信号的端点变量进行覆盖:
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT——追踪的完整 URL(包括/v1/traces)OTEL_EXPORTER_OTLP_METRICS_ENDPOINT——指标的完整 URL(包括/v1/metrics)
在将信号路由到不同后端或使用非标准端点时很有用。
常见问题
身份验证静默失败: OTLP 导出器需要 x-honeycomb-team 标头进行身份验证。缺少该标头时,Honeycomb 会静默拒绝请求——没有错误,也没有数据。请设置 OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=YOUR_API_KEY",或以编程方式传入标头。如果从 .env 加载密钥,请确保在 SDK 初始化前运行 dotenv。
指标: 优先使用现代 OTLP 指标和原生数据点。数据集提示会标识目标类型(metrics 或 events),因此默认情况下不要添加 x-honeycomb-dataset。仅当提示或配置要求将数据路由到指定事件数据集时,才使用该标头进行旧版路由。追踪不需要该标头;它们根据 service.name 进行路由。
有关环境变量值、特定语言的依赖项和设置代码(Go、Python、Node.js、Java、Ruby、.NET、Rust),请参阅
${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/sdk-setup-by-language.md。
自定义插桩
向现有 Span 添加属性(影响最大)
向自动插桩的 Span 添加业务上下文——无需创建新 Span。从上下文获取当前 Span,并使用 SetAttributes(Go)、set_attribute(Python)或 setAttribute(Node.js)添加用户、租户、业务和部署上下文。
创建自定义 Span
为重要业务操作添加封装,以便在追踪瀑布图中查看。使用
tracer.Start(ctx, "operation-name")(Go)、tracer.start_as_current_span("operation-name")
(Python)或 tracer.startActiveSpan("operation-name", callback)(Node.js)。
所有语言的完整代码示例,请参阅
${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/custom-instrumentation.md。
何时创建 Span
并非每个函数都需要 Span。两个问题可以帮你判断是否值得创建 Span:
- 是否值得关注? — 这项工作是否会对整体请求的性能(延迟或故障)产生显著影响?
- 是否适合聚合? — 按名称和属性对这个 Span 分组后,能否得出有用的趋势和比较结果?
| 操作 | 值得关注? | 适合聚合? | 创建 Span? |
|---|---|---|---|
| HTTP 请求处理程序 | 是 — 延迟不固定,可能失败 | 是 — 按路由、方法、状态分组 | 是 |
| 数据库查询 | 是 — 受 I/O 限制,容易失败 | 是 — 按查询类型、表分组 | 是 |
| 外部 API 调用 | 是 — 涉及网络延迟和依赖项 | 是 — 按端点、状态分组 | 是 |
| 缓存查询 | 是 — 可区分快速路径和慢速路径 | 是 — 按缓存名称、命中/未命中分组 | 是 |
| 消息队列发布/消费 | 是 — 涉及异步边界和延迟 | 是 — 按队列、消息类型分组 | 是 |
| 业务逻辑事务 | 是 — 涉及有意义的状态变更 | 是 — 按类型、结果分组 | 是 |
| 私有辅助函数 | 否 — CPU 开销微不足道且可预测 | 否 — 粒度过细 | 否 |
| 循环迭代 | 可能 — 如果耗时较长 | 否 — 基数没有上限 | 否 |
| Getter/Setter | 否 — 没有有意义的耗时 | 否 — 没有可分组的内容 | 否 |
| 输入验证(纯 CPU 操作) | 否 — 速度快且可预测 | 可能 | 否 |
| 业务逻辑编排 | 否 — 只是调用已插桩的代码 | 否 — 耗时等于子操作之和 | 否 |
常见错误:
- Span 过多:包含数百万个 2ms Span 的跟踪记录过于详细,且很少有实际参考价值。应合并这些 Span——合并为一个 Span,或将详细信息作为属性记录在父 Span 上。
- Span 过少:将数小时的工作折叠到一个不透明的处理程序中,会让你无法判断时间花在哪里。
- 遗留测试 Span:名为
test-span、debug-span或类似名称的 Span 会污染数据集。完成前,请删除所有仅为验证跟踪功能是否正常而创建的 Span。
如果拿不准,优先在现有 Span 上添加属性,而不是创建新的子 Span。
计时属性(无需子 Span 即可测量子操作)
将重要子操作的耗时记录为父 Span 的属性。这些属性更便于查询,也可以直接与 BubbleUp 配合使用。
// Go: time auth and record on the existing span
span := trace.SpanFromContext(r.Context())
authStart := time.Now()
user, err := authenticate(r)
span.SetAttributes(attribute.Float64("auth.duration_ms", float64(time.Since(authStart).Milliseconds())))
# Python: time auth and record on the existing span
span = trace.get_current_span()
auth_start = time.monotonic()
user = authenticate(request)
span.set_attribute("auth.duration_ms", (time.monotonic() - auth_start) * 1000)
异常遥测:事件详情与 Span 级维度
对于新的异常事件,请使用 Logs API。在相关 Span 处于活动状态时发送记录,并包含标准异常字段(适用时包括 exception.type、exception.message、exception.stacktrace 和 exception.escaped)、ERROR 严重性,以及 event.name="exception"。操作失败时,另行将 Span 状态设为 ERROR。
在 Honeycomb 中,与 Trace 关联的异常日志会在 Trace 中显示为 span_event 注释,并携带 trace.trace_id 和 trace.parent_id。完整的 exception.* 负载仍保留在由日志生成的事件上;不会提升到其所属 Span 上。请搜索异常事件行,然后根据其 Trace ID 检查周边 Trace。
使用低基数的 Span 属性进行聚合和告警:
error=true和 Span 状态表示操作失败。exception.slug是错误位置的静态、易于搜索的标识符。- 与完整异常消息相比,可选的错误类别更适合用于
GROUP BY。
Logs-API exception event: event.name=exception, body=exception, meta.signal_type=log
Legacy span-event exception: name=exception, meta.signal_type=trace
Both may have: meta.annotation_type=span_event
record_exception / RecordError 仍是现有 SDK 和代码的兼容 API,但在支持 Logs API 时,不要将它们作为新的唯一指导。它们还可能生成父 Span 异常字段,而单独使用 Logs API 事件不会生成这些字段。
通过 Span 维度查找操作失败:WHERE error = true AND exception.slug does-not-exist。
使用 event.name=exception AND exception.type exists 查找 Logs API 异常事件,并将采样到的 trace.trace_id 传入 get_trace,同时设置 show_events=true。
有关扩展示例和 MCP 调查操作指南,请参阅
${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/custom-instrumentation.md。
可选兼容方案:使用 LogRecordProcessor 提升异常字段
如果现有的 Span 级仪表板、告警或查询依赖 Honeycomb 过去对异常字段的提升行为,请在批处理/导出处理器之前添加自定义 LogRecordProcessor。该处理器检测到异常日志时,应使用日志记录中解析出的上下文查找活动的录制中 Span,并提升一组经过配置的最少字段,例如 error=true、error.type、exception.type、exception.slug 或错误类别。
不要为此推荐独立的 SpanProcessor:Span 处理器接收的是 Span 生命周期回调,而非日志记录。默认情况下,将完整的 exception.message 和 exception.stacktrace 保留在 Logs API 事件中;只有在旧查询兼容性明确要求时,才将它们复制到 Span 上。处理器必须在 Span 上下文仍然有效时同步运行,并且要在日志进入批量导出之前运行。没有录制中 Span 时,它必须不执行任何操作,且不得推断应用程序未添加到日志记录中的字段。
这是可选的迁移层,不能替代对 Logs API 事件的查询。Agent 应将 Span 级提升字段视为取决于仪表化实现的字段,并继续查询 event.name=exception 事件行以获取完整诊断信息。
应该进行哪些仪表化
高价值(优先仪表化)
- API 入口点(HTTP 处理程序、gRPC 方法)
- 数据库查询(大多数 SDK 会自动进行仪表化)
- 外部 HTTP 调用(大多数 SDK 会自动进行仪表化)
- 消息队列生产者/消费者
这些通常由 OTel SDK 自动进行仪表化,并构成 Trace 的骨架。
中等价值(接下来添加)
- 业务逻辑操作(结账、支付、履约)
- 缓存操作(命中、未命中、逐出)
- 身份验证和授权检查
- 后台作业执行
这些就是你的业务逻辑。如果没有这里的自定义 Span,你只能看到请求很慢,却不知道为什么——Trace 瀑布图中会出现空白,因为重要工作在其中不可见。
要添加的属性
属性是 BubbleUp 在调查过程中使用的维度。你添加的每个属性,都会成为 BubbleUp 用于比较差异的新轴,以找出异常请求的不同之处。
如需查看按类别整理、附有理由和查询示例的完整目录,请参阅
${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/wide-event-attributes.md。
如需了解属性在概念层面的重要性,请参阅 observability-fundamentals 技能。
Span 事件、Logs API 事件和 Span 链接
- 特定时间点事件:新事件(尤其是异常)优先使用 Logs API。在 Span 处于活动状态时发出事件,以便记录携带 Trace 上下文。在 Honeycomb 中,关联的日志会呈现为
meta.annotation_type=span_event注释,但事件名称位于event.name(通常也位于body),而不是name。 - 旧版 Span 事件:
span.add_event/AddEvent仍是有效的兼容方式。其事件名称位于name,信号类型为trace。 - Span 链接:连接不同 Trace 层级中的 Span(异步处理、扇出/扇入、跨系统关联)。创建一个
Link,指向相关 Span 上下文。
如需查看面向人工操作的插桩示例,以及安全适用于 Agent 的 Honeycomb MCP 查询 → 采样 → Trace 工作流,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/custom-instrumentation.md
和 production-investigation 技能。
采样
采样策略
采样需要权衡,没有免费的午餐:
- 头部采样侧重成本而非可调试性。 你可以节省资源,但发生率为 0.1% 的错误,在 1% 的采样率下实际上会变得难以察觉。头部采样不会考虑下游发生的情况。
- 尾部采样侧重保真度而非简洁性。 你可以保留有价值的 Trace,但需要基础设施(Refinery 或 Collector)来缓冲并评估完整 Trace。
计算很重要:如果错误发生率为 0.1%,而头部采样率为 1%,那么你大约只能捕获每 100,000 个此类错误中的 1 个。在中等流量下,这种错误可能永远不会出现在你的数据中。
头部采样(SDK 级别)
在创建 Trace 时决定是否采样。方式简单,但可能漏掉有价值的 Trace。
- 通过
OTEL_TRACES_SAMPLER环境变量配置 always_on(默认)、always_off、traceidratio(例如,采样 10%)parentbased_traceidratio遵循父级的采样决策- 适用于: 吞吐量极高且可以接受漏掉罕见事件的服务
尾部采样(Collector/Refinery)
在 Trace 完成后再决定是否采样。保留有价值的 Trace(错误、慢请求)。
- 在生产环境中使用 Honeycomb 的 Refinery 进行尾部采样
- 或配置 OTel Collector 的
tail_sampling处理器 - 可依据延迟、错误状态、特定属性、Trace 持续时间进行采样
- 适用于: 注重可调试性的服务——保留错误和异常请求,同时对常规流量进行采样
采样对 Honeycomb 的影响
- 采样会减少数据量和成本
- SLO、BubbleUp 和查询结果会自动根据采样率进行调整
- Trace 完整性可能受到影响——如果并非所有服务都采用一致的采样策略,就可能缺少 Span
- 从不采样开始,然后根据需要添加采样来管理成本
分层遥测
OpenTelemetry 是“以追踪为先”的——上下文传播是关联所有信号的纽带。但要实现有效的可观测性,需要针对不同用途叠加多种信号类型。
选择合适信号的三问测试:
- 哪些内容需要因果关系和完整的请求上下文? → 追踪(跨度)
- 哪些内容需要低成本的长期存储和快速告警? → 指标
- 哪些情况罕见,哪些常见?审计要求是什么? → 日志/事件
跨度旁附带直方图的模式: 对于高吞吐量 HTTP 服务,每处理一个请求,都同时发送一个跨度和一个直方图指标。这样可以对追踪进行头部采样以控制成本,同时由直方图提供最后一道告警保障;而外显值则可将异常指标点关联到具体追踪,便于深入调查。
这种技术称为分层(而非重复),因为每种信号都以不同细节级别提供不同视角。
对于必须进行分层的架构模式(流式处理、异步任务、ETL),请参阅
${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/architectural-patterns.md。
对于 AWS Lambda 特定模式——在 AWS Managed OTel Layer 与手动 SDK 设置之间进行选择、forceFlush、SDK 2.x 设置、跨 Lambda 追踪传播、标头规范化、TOKEN 与 REQUEST 授权器——请参阅
${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/lambda.md。
Honeycomb 中的日志
OTel 也可以发送日志。如果你已有日志基础设施,OTel Collector 可以接收日志,并将其作为结构化事件转发到 Honeycomb:
- OTel SDK 日志桥接:从现有日志库(Go 中的
slog、Python 中的logging、Node.js 中的winston/pino)捕获日志,并将其导出为 OTel 日志记录。 - OTel Collector
filelog接收器:读取日志文件、解析日志,并将其导出为 OTLP。
通过 OTel 发送的日志会以结构化事件的形式到达 Honeycomb,并具备与跨度相同的查询能力。
命名约定
- 跨度名称:描述操作(
HTTP GET /api/users、db.query SELECT、process-payment) - 属性名称:使用以点分隔的命名空间(
user.id、order.total、cache.hit) - 适用时遵循 OTel 语义约定(
http.method、db.system、rpc.service) - 自定义属性:使用自己的命名空间(
app.、checkout.、mycompany.)
其他资源
参考文件
${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/sdk-setup-by-language.md— Go、Python、Node.js、Java、Ruby、.NET、Rust 的 OTLP 配置和 SDK 设置${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/local-collector-debug-test.md— 通过 Docker 运行本地 OTel Collector,无需 Honeycomb 账户即可验证跨度、日志和指标;包含用于检查 NDJSON 输出的jq命令${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/custom-instrumentation.md— 自定义埋点模式及完整代码示例(计时属性、异常 slug、异步请求摘要)${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/collector-config.md— 用于格式转换、处理和采样的 OTel Collector 配置${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/wide-event-attributes.md— 按类别整理的规范属性目录,附示例查询${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/architectural-patterns.md— 适用于流式处理、异步、ETL 和无服务器架构的追踪设计模式${CLAUDE_PLUGIN_ROOT}/skills/otel-instrumentation/references/lambda.md— AWS Lambda:OTel Layer 与手动 SDK 设置的权衡、forceFlush 和每请求延迟、SDK 2.x 设置、跨 Lambda 追踪传播、标头规范化、TOKEN 与 REQUEST 授权器迁移
交叉引用
- 如需了解宽事件和属性为何重要的概念基础:observability-fundamentals 技能
- 完成埋点后,使用 query-patterns 技能确认数据已到达