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-07 | PR #799 | Schniz | CLOSED | 最早添加 OTEL 用于性能调试 |
| 2025-08 | PR #1738 | thdxr | MERGED | 移除了未使用的 OTEL 追踪代码 |
| 2025-09 | PR #2735 | BrianVandenberg | CLOSED | AI SDK OTEL Instrumentation,反响平平 |
| 2025-12-05 | PR #4978 | noamzbr | MERGED | 首个合并的 OTEL PR,添加 experimental.open_telemetry 配置 |
| 2025-12-08 | PR #5245 | tianhuil | OPEN | 完整集成提案,多人确认开箱不可用 |
| 2026-01-02 | PR #6629 | Hona (Luke Parker) | OPEN (Draft) | 227 commits,最全面方案,Aspire Dashboard 支持 |
| 2026-02-13 | Issue #13438 | 社区 | — | 即使启用 openTelemetry: true 也无 traces 输出 |
| 2026-04-07 | PR #21387 | thdxr | MERGED | 突破性 PR:Effect 服务层 OTLP 导出 |
| 2026-04-10 | PR #21799 | kitlangton | CLOSED | 全局 Tracer 桥接(被后续 PR 取代) |
| 2026-04-15 | PR #22526 | kitlangton | MERGED | 修复 AI SDK telemetry spans 导出 |
| 2026-04-15 | PR #22645 | kitlangton | MERGED | 注册 Context Manager,修复 span 父子关系断裂 |
| 2026-04-15 | PR #22653 | kitlangton | MERGED | 请求级别 route spans |
| 2026-04-17 | PR #23156 | kitlangton | MERGED | 移除流式事件热路径的低价值 spans |
| 2026-04-18 | PR #23213 | kitlangton | MERGED | 标准化 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:
- PR #22526 — 修复 AI SDK telemetry spans 导出,将 Effect OTel tracer 传入 AI SDK
- PR #22645 — 注册 AsyncLocalStorageContextManager,修复了关键的 span 父子关系断裂 bug——修复前每个 AI SDK span 都是孤立的 trace;修复后 400+ spans 正确串联在同一个 traceId 下
- PR #22653 — 添加请求级别的 route spans
- PR #23156 — 移除流式事件热路径的低价值 spans(避免 thousands of noise spans)
- 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 | 备注 |
|---|---|---|
| Jaeger | http://localhost:4318 | 本地开发首选 |
| Grafana Tempo | http://localhost:4318 | 配合 Grafana Dashboard |
| SigNoz | https://ingest.us.signoz.cloud:443 | 开源 APM,官方教程 |
| Honeycomb | https://api.honeycomb.io | SaaS,需设置 API Key |
| Datadog | https://api.datadoghq.com | 通过 OTLP ingest |
| .NET Aspire Dashboard | http://localhost:18889 | PR #6629 专为此设计 |
| Langfuse | 通过专用插件 | 直接 SDK 集成 |
⚠️ 常见陷阱
experimental.openTelemetry: true单独不够 — 它只启用 AI SDK span 生成。你还需要设置OTEL_EXPORTER_OTLP_ENDPOINT(用于原生导出)或安装一个初始化 NodeSDK 的插件。- 原生层使用 OTLP HTTP(端口 4318),而
@devtheops/opencode-plugin-otel使用 OTLP gRPC(端口 4317)。两者可以同时运行,互不干扰。 - 项目仍在快速迭代——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)做了堆叠分解,哪个组件在什么时间消耗最多资源,清清楚楚。

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

输出速率与 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,展现了请求间巨大的差异。

以上三张图都是在一次普通的 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 个月的等待是值得的。