OpenCode 终于等来了 OTEL

如果你一直在关注 OpenCode 的 GitHub 仓库,你大概会注意到一个反复出现的主题:OpenTelemetry 支持什么时候才能真正用起来?

这个问题困扰了社区将近一年。从 2025 年夏天第一个实验性 PR 开始,到配置选项加进去却什么也不输出,到社区反复提交提案又被搁置,再到用户在 Issue 里困惑地问「为什么我按文档配了还是看不到 traces」——OTEL 在 OpenCode 里的集成之路,走得一点也不顺利。

直到 2026 年 4 月,情况才迎来转机。4 月 7 日,一个精简的 OTLP 导出 PR 悄然合并;随后一周内,核心贡献者 kitlangton 连续提交了 5 个修复 PR,补齐了 span 桥接、context 传播、属性标准化等关键环节。十年磨一剑有点夸张,但近十个月的等待,终于有了结果。

这篇文章,我想从头梳理这段历程——不只是冷冰冰的时间线,还有那些「为什么这么难」背后的技术原因,以及现在你到底该怎么接入、能获得什么。如果你也在用 OpenCode,我希望这篇文章能帮你少走弯路。

OTEL 合并时间线

从 2025 年 7 月第一个实验性 PR,到 2026 年 4 月中旬一周内连合 6 个关键 PR,OpenCode 的 OTEL 集成走过了近 10 个月的曲折路程。以下是关键里程碑:

时间PR / Issue作者状态关键内容
2025-07PR #799SchnizCLOSED最早添加 OTEL 用于性能调试
2025-08PR #1738thdxrMERGED移除了未使用的 OTEL 追踪代码
2025-09PR #2735BrianVandenbergCLOSEDAI SDK OTEL Instrumentation,反响平平
2025-12-05PR #4978noamzbrMERGED首个合并的 OTEL PR,添加 experimental.open_telemetry 配置
2025-12-08PR #5245tianhuilOPEN完整集成提案,多人确认开箱不可用
2026-01-02PR #6629Hona (Luke Parker)OPEN (Draft)227 commits,最全面方案,Aspire Dashboard 支持
2026-02-13Issue #13438社区即使启用 openTelemetry: true 也无 traces 输出
2026-04-07PR #21387thdxrMERGED突破性 PR:Effect 服务层 OTLP 导出
2026-04-10PR #21799kitlangtonCLOSED全局 Tracer 桥接(被后续 PR 取代)
2026-04-15PR #22526kitlangtonMERGED修复 AI SDK telemetry spans 导出
2026-04-15PR #22645kitlangtonMERGED注册 Context Manager,修复 span 父子关系断裂
2026-04-15PR #22653kitlangtonMERGED请求级别 route spans
2026-04-17PR #23156kitlangtonMERGED移除流式事件热路径的低价值 spans
2026-04-18PR #23213kitlangtonMERGED标准化 session telemetry 属性

第一阶段:萌芽与清理(2025 年 7-8 月)

OTEL 在 OpenCode 中的历史充满了戏剧性。最早的尝试可以追溯到 2025 年 7 月,Schniz 提交了 PR #799 添加 OTEL 用于性能调试。一个月后,thdxr 提交了 PR #1738——反其道而行之,移除了未使用的 OTEL 追踪代码。这个「先加后删」的循环预示了后续的曲折。

第二阶段:从零开始(2025 年 9-12 月)

PR #2735(BrianVandenberg)尝试为 AI SDK 添加 OTEL Instrumentation,采用 opt-in 模式,但反响平平。随后 PR #4978(noamzbr)于 12 月 5 日合并,添加了 experimental.open_telemetry 配置选项——这是第一个真正被合并的 OTEL PR。

但问题在于:这个配置只是个开关,它没有初始化 OTEL SDK。正如 PR #5245(tianhuil)的提交者所发现的,协作者声称 experimental.openTelemetry: true 应该能工作,但实际开箱即用不了。多位用户报告了相同的问题,这条 PR 至今仍处于 OPEN 状态。

PR #6629(Hona / Luke Parker)是社区最雄心勃勃的尝试:227 个 commits,覆盖所有工具调用、MCP 通信、Session 生命周期、LLM 调用链路。Hona 在 2026 年 3 月的 Aspire Conf 上做了演示。然而,它至今仍为 Draft 状态——可能正因为太大而难以 review。

第三阶段:痛点爆发(2026 年 2 月)

一系列 Issue 暴露了集成的核心问题:

  • Issue #13438:用户按文档配置了 experimental.openTelemetry: true,OTLP traces 根本没发出
  • Issue #12142:请求 OTEL export 能力
  • Issue #14697:请求原生 OTLP export
  • Issue #14246:希望像 Claude Code 一样支持 OTEL

根因是 Effect 的内部 Tracer 没有注册为全局 Provider,导致 AI SDK 的 spans 被静默丢弃。用户看到配置选项,打开它,却什么都得不到。

第四阶段:突破与合并周(2026 年 4 月 7-18 日)

2026 年 4 月 7 日,thdxr 提交了 PR #21387——使用 Effect 的 OTLP layer 提供 Effect 服务层的 span 导出。同日合并。这是第一个成功合并的生产级 OTEL PR。

随后,kitlangton 在一周内连续提交并合并了 5 个关键 PR:

  1. PR #22526 — 修复 AI SDK telemetry spans 导出,将 Effect OTel tracer 传入 AI SDK
  2. PR #22645 — 注册 AsyncLocalStorageContextManager,修复了关键的 span 父子关系断裂 bug——修复前每个 AI SDK span 都是孤立的 trace;修复后 400+ spans 正确串联在同一个 traceId 下
  3. PR #22653 — 添加请求级别的 route spans
  4. PR #23156 — 移除流式事件热路径的低价值 spans(避免 thousands of noise spans)
  5. PR #23213 — 标准化 session telemetry 属性(sessionID → session.id)

这周是 OpenCode OTEL 的「破局周」。从无到有,从断裂到完整,仅用 12 天。

重要意义

1. 开源 AI Agent 可观测性的标杆

OpenCode 不是第一个支持 OTEL 的 AI 工具(Claude Code 先行一步),但它是开源 AI Agent 中第一个提供完整、生产级 OTEL 集成的项目。社区从此可以:

  • 搭建自己的可观测性栈(不需要付费 SaaS)
  • 自定义 Dashboard 和告警
  • 在 CI/CD 中嵌入 Agent 性能回归检测

2. Effect-TS 与 OTEL 的桥接范式

OpenCode 的技术架构基于 Effect-TS。Effect 有自己的 tracing 系统,但它不是 OTEL 原生的。PR #22645 的核心贡献是注册了 AsyncLocalStorageContextManager,让 Effect 的内部 tracer 和 AI SDK 的 OTEL tracer 共享同一个 context——两个系统的 span 无缝融合

修复前:每个 AI SDK span 都是孤立的 trace,Effect spans 和 AI SDK spans 各自为政。

修复后:400+ spans 正确串联在同一个 traceId 下,形成完整的调用链路。这个模式对其他使用 Effect-TS 的项目有重要的参考价值。

3. AI Agent 语义约定的雏形

社区插件已经在定义 AI Agent 的标准化指标:

  • session.count — 会话计数
  • token.usage — Token 消耗(input/output/reasoning/cache)
  • cost.usage — 成本追踪(USD)
  • lines_of_code.count — 代码行数变更
  • tool.duration — 工具执行耗时
  • cache.count — 缓存命中率
  • model.usage — 模型使用分布

这些指标正在成为 AI Agent 可观测性的 de facto 标准,与 OpenTelemetry 的 GenAI 语义约定(Semantic Conventions)逐渐对齐。

技术架构

OpenCode 的 OTEL 集成采用了双层设计:

  • Effect-TS OTEL Layer:由 OTEL_EXPORTER_OTLP_ENDPOINT 环境变量激活,提供 Effect 服务层的 span 和 log 导出
  • AI SDK Telemetry:由 experimental.openTelemetry 配置项激活,提供 LLM 调用的详细 span(模型名、Token 用量、流式事件等)

两层通过 AsyncLocalStorageContextManager 共享 context,实现 span 的父子关系串联。

关键设计决策:

  • 零开销:未配置 endpoint 时,OTEL SDK 不会加载,无任何运行时成本
  • 批处理导出:使用 BatchSpanProcessor 而非 Simple,保证性能
  • 延迟加载:OTEL SDK 仅在检测到环境变量时才动态初始化
  • 标准协议:使用 OTLP HTTP 导出,兼容任何 OTLP 后端

导出的 Resource Attributes:

{
  "service.name": "opencode",
  "service.version": "<version>",
  "deployment.environment.name": "stable|beta|local",
  "opencode.client": "cli|desktop",
  "opencode.process_role": "main|tui|server",
  "opencode.run_id": "<uuid>"
}

如何接入与使用

方式一:原生集成(推荐入门)

Step 1:启动 OTLP 后端

最简单的方式是用 Jaeger 的 all-in-one Docker 镜像:

docker run -d --name jaeger \
  -p 4318:4318 \
  -p 16686:16686 \
  jaegertracing/jaeger:latest

这会在 4318 端口提供 OTLP HTTP 接收器,在 16686 端口提供 Jaeger UI。

Step 2:设置环境变量

# 必需 — 设置后 OTEL 自动激活
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

# 可选 — 认证头(用于 SaaS 后端)
export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"

# 可选 — 自定义资源属性
export OTEL_RESOURCE_ATTRIBUTES="host.name=my-dev-machine"

Step 3:启用 AI SDK 遥测(可选,推荐)

opencode.json 中添加:

{
  "$schema": "https://opencode.ai/config.json",
  "experimental": {
    "openTelemetry": true
  }
}

这会额外捕获 AI SDK 级别的 span:模型名称、Token 用量、流式事件、工具调用参数等。

Step 4:启动 OpenCode 并查看

opencode

打开 http://localhost:16686,选择 opencode service,你将看到完整的调用链路:

  • SessionProcessor.create — 会话创建
  • SessionProcessor.process — 消息处理
  • LLM.run / Provider.chat — LLM 调用(含 Token 用量)
  • Tool.run — 工具执行
  • ai.streamText — AI SDK 流式响应
  • ai.toolCall — AI SDK 工具调用

方式二:社区插件(推荐进阶)

原生集成提供了 Traces 和 Logs,但社区插件额外提供了结构化的 Metrics。

@devtheops/opencode-plugin-otel — 最流行的 OTEL 插件

{
  "$schema": "https://opencode.ai/config.json",
  "experimental": { "openTelemetry": true },
  "plugin": ["@devtheops/opencode-plugin-otel"]
}
export OPENCODE_ENABLE_TELEMETRY=1
export OPENCODE_OTLP_ENDPOINT=http://localhost:4317  # 注意:gRPC 端口

提供丰富的 Metrics:session.count, token.usage, cost.usage, lines_of_code.count, commit.count, tool.duration, cache.count, model.usage, retry.count 等。通过 OTLP/gRPC 导出。

opencode-plugin-langfuse — Langfuse 集成

{
  "experimental": { "openTelemetry": true },
  "plugin": ["opencode-plugin-langfuse"]
}
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."

零配置 Langfuse 追踪,捕获 sessions、messages、tool calls、costs 和性能数据。

@gcornut/opencode-otel — 最可配置的插件

使用 JSON 配置文件而非环境变量,支持 gRPC、HTTP/JSON、HTTP/protobuf 三种协议,有 telemetryProfile: "claude-code" 模式兼容 Claude Code Dashboard。

pai4451/opencode-telemetry-plugin — 详细的 LOC 追踪

零配置插件,80+ 语言检测,LOC 追踪,DELTA 时间聚合。

后端兼容性

后端Endpoint备注
Jaegerhttp://localhost:4318本地开发首选
Grafana Tempohttp://localhost:4318配合 Grafana Dashboard
SigNozhttps://ingest.us.signoz.cloud:443开源 APM,官方教程
Honeycombhttps://api.honeycomb.ioSaaS,需设置 API Key
Datadoghttps://api.datadoghq.com通过 OTLP ingest
.NET Aspire Dashboardhttp://localhost:18889PR #6629 专为此设计
Langfuse通过专用插件直接 SDK 集成

⚠️ 常见陷阱

  1. experimental.openTelemetry: true 单独不够 — 它只启用 AI SDK span 生成。你还需要设置 OTEL_EXPORTER_OTLP_ENDPOINT(用于原生导出)或安装一个初始化 NodeSDK 的插件。
  2. 原生层使用 OTLP HTTP(端口 4318),而 @devtheops/opencode-plugin-otel 使用 OTLP gRPC(端口 4317)。两者可以同时运行,互不干扰。
  3. 项目仍在快速迭代——Issue #14697 请求将 endpoint/protocol/headers 作为顶层配置而非环境变量,尚未合并。

实战效果:我的 Grafana Dashboard

说了这么多,你可能在想:接入之后到底能看到什么?我用自己的 Grafana + Tempo 接了一下,以下是我实际看到的。

Model Operations 总览

这是接上 OTEL 后的第一个惊喜——你终于能看到 Agent 在「做什么」了。Active Sessions、Model Usage、Top Operations 三个核心指标一目了然。下方的 Operations/sec 时间序列图按操作类型(Agent、LLM、Plugin、Provider、Session、streamText、toolCall)做了堆叠分解,哪个组件在什么时间消耗最多资源,清清楚楚。

Grafana Model Operations Dashboard

Token 消耗分布

这一张更实用。Input Tokens、Output Tokens、Cached Input Tokens 分得明明白白。你可以看到缓存在发挥作用——44k Cached Input vs 708 实际 Input,缓存命中率 98.5%。Token Trends 图让你追踪每次请求的 Token 消耗趋势,对比高峰和低谷。

Grafana Token Distributions Dashboard

输出速率与 Session 分析

最让我震撼的是这张。Output Token Rate 曲线直观地展示了 Agent 的「思考速度」——峰值 27 tokens/sec,不是均匀的,而是脉冲式的。Per-Session Analysis 里的 Cache Hit Ratio 达到了 98.5%,说明大部分 Prompt Context 都被缓存命中了,这也是为什么实际 Token 成本远低于理论值。Max Input 47k tokens、Min Input 706 tokens,展现了请求间巨大的差异。

Grafana Output Token Rate Dashboard

以上三张图都是在一次普通的 OpenCode 编码会话中采集到的。配置只花了 5 分钟——设一个环境变量,启动 Grafana + Tempo,打开 OpenCode,数据就来了。这就是 OTEL 的价值:你不用猜了

写在最后

回头看这段历程,颇有几分戏剧性:2025 年 7 月有人加了 OTEL,8 月就被删了;社区反复提交提案,最大的那个(227 commits)至今还在 Draft 状态;用户按照文档配置,却什么 traces 都看不到——Effect 的内部 tracer 和 OTEL 的全局 provider 之间,差了一座桥。

最终解决问题的方式,反而是最朴素的:不大包大揽,而是一块一块地补。PR #21387 先把 Effect 层的导出接上,然后 PR #22645 注册 context manager 把 AI SDK 的 spans 串起来,再逐步打磨细节。这种小步快跑的方式,比一开始就追求完美方案有效得多。

对 OpenCode 社区来说,OTEL 不仅仅是一个技术特性。它意味着 AI Agent 正在从「能用」走向「可信」——当你能在 Dashboard 上看到 Agent 的每一步操作、每一次 LLM 调用的完整链路、每一分钱的成本消耗,这个黑盒就不再是黑盒了。

如果你正在用 OpenCode,花 5 分钟配上 OTEL 吧。当你第一次在 Grafana 或 Jaeger 里看到 Agent 的完整调用链路、Token 消耗曲线和缓存命中率时,你会觉得这 10 个月的等待是值得的。