Claude Code OpenTelemetry 可观测性体系深度分析
Claude Code OpenTelemetry 可观测性体系深度分析
1. 设计哲学总览
Claude Code 作为一个 AI Agent 级别的工具,其 OpenTelemetry(OTel)可观测性体系的设计哲学可以概括为:
“分层递进、按需激活、多管齐下、隐私优先”
- 分层递进(Layered Telemetry):可观测性被分为三个独立层次——Metrics(指标)、Logs/Events(事件)、Traces(链路追踪),各自独立配置、独立导出,互不干扰。
- 按需激活(Opt-in Activation):不同级别的可观测性通过不同环境变量和 Feature Gate 控制,从”默认开启的基础指标”到”需要显式启用的详细链路追踪”,形成阶梯式激活策略。
- 多管齐下(Multi-Backend):同一份采集数据可以同时导出到多个后端(OTLP、BigQuery、Prometheus、Perfetto、内部 1P 分析系统),每个后端有专门的 Exporter 实现。
- 隐私优先(Privacy-First):默认对用户 Prompt 做脱敏处理(
<REDACTED>),仅在用户显式 opt-in 时才记录原文;不同用户身份(antvs 外部)看到的追踪细节不同。
2. 架构总览
┌─────────────────────────────────────────────────────────────────┐ |
3. 核心文件清单
| 文件路径 | 职责 |
|---|---|
src/utils/telemetry/instrumentation.ts |
中枢初始化器:OTel SDK 的 Resource、Provider、Exporter 装配,启动/关闭生命周期 |
src/utils/telemetry/sessionTracing.ts |
链路追踪引擎:Span 创建/结束、父子关系、AsyncLocalStorage 上下文传播 |
src/utils/telemetry/betaSessionTracing.ts |
Beta 详细追踪:System Prompt/Tool Schema 去重日志、增量 new_context 计算 |
src/utils/telemetry/events.ts |
事件发射器:logOTelEvent() 通过 OTel Logs API 发射结构化事件 |
src/utils/telemetry/bigqueryExporter.ts |
自定义 Metrics Exporter:将 OTel 指标序列化为 BigQuery 格式并通过 HTTP POST 上报 |
src/utils/telemetry/perfettoTracing.ts |
Perfetto 追踪:Chrome Trace Event 格式,输出可在 ui.perfetto.dev 可视化的 JSON |
src/utils/telemetry/logger.ts |
OTel 诊断日志桥:将 OTel 内部诊断信息桥接到应用调试日志 |
src/utils/telemetryAttributes.ts |
公共属性工厂:构建所有遥测信号的通用维度(user.id、session.id 等) |
src/bootstrap/state.ts |
全局状态:持有 MeterProvider、LoggerProvider、TracerProvider 以及各 Counter 实例 |
src/services/analytics/firstPartyEventLogger.ts |
第一方事件系统:独立于客户 OTLP 的内部分析事件管道 |
src/services/analytics/firstPartyEventLoggingExporter.ts |
第一方事件 Exporter:带 backoff 重试、磁盘持久化的自定义 LogRecordExporter |
src/entrypoints/init.ts |
启动入口:延迟加载 OTel 模块、创建 Meter 和 Counter 实例 |
4. 初始化流程(Initialization Pipeline)
4.1 启动时序
应用启动 |
4.2 延迟加载策略
这是一个极其重要的设计决策——OTel 模块总体积约 1.2MB+,为了不拖慢启动速度:
// init.ts 中通过动态 import 延迟加载 |
更进一步,gRPC Exporter(~700KB @grpc/grpc-js)在 instrumentation.ts 内部按协议类型按需动态导入:
// instrumentation.ts — 只在协议为 grpc 时才加载 |
设计意图:一个进程最多只使用一种协议变体,但静态导入会一次加载全部 6 个 Exporter 模块。通过 switch-case + dynamic import,将实际加载量降到最小。
4.3 Resource 构建
Resource 是 OTel 中标识遥测信号来源的元数据结构。Claude Code 构建了一个多层合并的 Resource:
const baseResource = resourceFromAttributes({ |
4.4 环境变量体系
Claude Code 定义了一套完整的环境变量体系来控制遥测行为:
| 环境变量 | 作用 | 默认值 |
|---|---|---|
CLAUDE_CODE_ENABLE_TELEMETRY |
总开关:启用第三方 OTLP 遥测导出 | false |
OTEL_METRICS_EXPORTER |
Metrics Exporter 类型(console, otlp, prometheus) |
none |
OTEL_LOGS_EXPORTER |
Logs Exporter 类型(console, otlp) |
none |
OTEL_TRACES_EXPORTER |
Traces Exporter 类型(console, otlp) |
none |
OTEL_EXPORTER_OTLP_PROTOCOL |
OTLP 协议(grpc, http/json, http/protobuf) |
— |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP 收集器地址 | — |
OTEL_EXPORTER_OTLP_HEADERS |
OTLP 请求头 | — |
OTEL_LOG_USER_PROMPTS |
允许在追踪中记录用户 prompt 原文 | false |
OTEL_LOG_TOOL_CONTENT |
允许记录工具输入/输出内容 | false |
ENABLE_BETA_TRACING_DETAILED |
启用 Beta 详细追踪 | false |
BETA_TRACING_ENDPOINT |
Beta 追踪上报地址 | — |
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA |
增强遥测(Feature Gate) | 由 GrowthBook 控制 |
CLAUDE_CODE_PERFETTO_TRACE |
Perfetto 本地追踪 | false |
CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS |
关闭超时 | 2000 |
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE |
指标时间粒度 | delta |
Ant(内部用户)特殊路径:ANT_OTEL_* 前缀的变量在构建时注入,运行时由 bootstrapTelemetry() 映射为标准 OTEL_*。
5. 三大信号深度解析
5.1 Metrics(指标)
5.1.1 业务指标定义
Claude Code 定义了 8 个核心业务 Counter,全部注册在 com.anthropic.claude_code 命名空间下:
| 指标名 | 描述 | 单位 |
|---|---|---|
claude_code.session.count |
CLI 会话启动次数 | — |
claude_code.lines_of_code.count |
代码行修改数(含 type=added/removed 维度) | — |
claude_code.pull_request.count |
创建的 PR 数量 | — |
claude_code.commit.count |
创建的 commit 数量 | — |
claude_code.cost.usage |
会话 API 调用成本 | USD |
claude_code.token.usage |
Token 消耗量 | tokens |
claude_code.code_edit_tool.decision |
代码编辑工具的决策分布 | — |
claude_code.active_time.total |
活跃时间 | seconds |
5.1.2 Counter 的封装设计
Counter 被封装为 AttributedCounter 类型,它在 OTel Counter 之上附加公共维度属性:
export type AttributedCounter = { |
设计亮点:每次 add() 调用时,自动注入 user.id、session.id、organization.id 等公共维度,业务代码无需关心属性传递。
5.1.3 基数控制(Cardinality Control)
指标维度的基数(cardinality)是 OTel Metrics 的核心挑战。Claude Code 通过环境变量控制哪些高基数维度被包含:
const METRICS_CARDINALITY_DEFAULTS = { |
对于事件来说,prompt.id 和 workspace.host_paths 只附加到 Events 上,而不附加到 Metrics 上,因为它们会造成无界基数。
5.1.4 时间粒度:Delta vs Cumulative
// 默认设置为 delta —— 这是一个慎重的决定 |
BigQuery Exporter 中甚至有一条注释:
selectAggregationTemporality(): AggregationTemporality { |
Delta 模式在 CLI 工具场景下更合理——每个进程短暂存活,报告的是”增量变化”而非”累积总量”。
5.1.5 多 Exporter 并行
Metrics 支持同时启用多个 Exporter,通过 PeriodicExportingMetricReader 包装:
- OTLP Exporter:标准 OTel 协议导出(支持 gRPC/HTTP JSON/HTTP Protobuf)
- BigQuery Exporter:自定义
PushMetricExporter,POST 到api.anthropic.com/api/claude_code/metrics - Prometheus Exporter:暴露 pull 端点
- Console Exporter:调试用
BigQuery Exporter 有更长的导出间隔(5 分钟 vs 默认 60 秒),用以降低后端负载。
5.2 Logs/Events(结构化事件)
5.2.1 双轨事件系统
Claude Code 有两套独立的事件日志系统,各有不同用途:
第一轨:第三方 OTLP 事件(events.ts)
通过 OTel Logs API 的 Logger.emit() 发射,导出到客户配置的 OTLP 后端:
export async function logOTelEvent( |
典型事件包括:user_prompt、system_prompt、tool(tool schema 首次出现时)等。
第二轨:第一方内部分析事件(firstPartyEventLogger.ts)
完全独立于客户遥测管道,使用自己的 LoggerProvider:
// IMPORTANT: We must get the logger from our local provider, not logs.getLogger() |
事件通过自定义 FirstPartyEventLoggingExporter 以 protobuf 格式 POST 到 /api/event_logging/batch。
设计哲学:将内部分析数据与客户可见的遥测数据严格隔离——内部事件永远不会泄露到客户的 OTLP 端点,反之亦然。
5.2.2 事件采样策略
第一方事件支持通过 GrowthBook 远程配置的采样率控制:
export type EventSamplingConfig = { |
未配置的事件 100% 记录,sample_rate=0 则完全丢弃。
5.2.3 事件持久化与重试
FirstPartyEventLoggingExporter 实现了完善的故障恢复机制:
- 追加式写入:失败事件以 JSONL 格式追加到
~/.claude/telemetry/1p_failed_events.<sessionId>.<batchUUID>.json - 二次方退避重试:
delay = baseDelay × attempts²,最大 30 秒 - 跨进程恢复:启动时扫描同 session 的历史失败文件,在后台重试
- 短路优化:一个批次失败后,跳过后续所有批次,避免向不健康的端点白费请求
- 最大尝试次数:默认 8 次后丢弃
5.3 Traces(分布式链路追踪)
这是 Claude Code OTel 实现中最精华的部分,它完整地追踪了 Agent 执行链路。
5.3.1 Span 层次结构
claude_code.interaction (root span) |
每个 Span 类型对应一个 SpanType:
type SpanType = |
5.3.2 上下文传播机制:AsyncLocalStorage
传统 OTel 在 Node.js 中使用 AsyncResource 或全局 Context API 传播 trace context。Claude Code 做了一个非典型但实用的选择——使用 AsyncLocalStorage 管理 Span 上下文:
const interactionContext = new AsyncLocalStorage<SpanContext | undefined>() |
为什么?
- Agent 的 LLM 请求和工具执行有严格的嵌套层次:
interaction → llm_request/tool - LLM 请求可能并行执行(warmup 请求、分类器、主线程),ALS 提供自然的作用域隔离
- 比 OTel 标准 Context API 更轻量,不需要通过
context.with()传播
父子关系通过手动设置 OTel Context 实现:
const ctx = parentSpanCtx |
5.3.3 Span 生命周期管理
创建与跟踪:
const activeSpans = new Map<string, WeakRef<SpanContext>>() |
两个 Map 的分工:
activeSpans:所有活跃 Span 的弱引用——当 Span 被 ALS 释放且无其他引用时,GC 可回收strongSpans:不在 ALS 中存储的 Span(LLM request、blocked-on-user、tool execution、hook)需要强引用防止被 GC
自动清理:
const SPAN_TTL_MS = 30 * 60 * 1000 // 30 分钟 |
设计亮点:
unref()确保定时器不会阻止 Node.js 自然退出- WeakRef + 定时清理 = 零内存泄漏风险
- 30 分钟 TTL 作为异常场景的兜底(如未捕获异常导致 Span 未关闭)
5.3.4 Span 属性丰富度
以 LLM Request Span 为例,结束时记录的元数据:
endLLMRequestSpan(llmSpan, { |
5.3.5 Beta 详细追踪的增量上下文机制
Beta 追踪(betaSessionTracing.ts)引入了一个精妙的设计——基于 Hash 的增量上下文追踪。
问题:Agent 的每轮 LLM 请求都携带完整的对话历史,如果每次都记录完整历史,追踪数据量会爆炸式增长。
解决方案:
// 跟踪每个 Agent(querySource)最后上报的消息 Hash |
同样的思路也应用于 System Prompt 去重:
const seenHashes = new Set<string>() |
隐私可见性控制矩阵:
| 内容 | External 用户 | Ant 内部用户 |
|---|---|---|
| System Prompt | ✅ | ✅ |
| Model Output | ✅ | ✅ |
| Thinking Output | ❌ | ✅ |
| Tools | ✅ | ✅ |
| new_context | ✅ | ✅ |
5.3.6 内容截断与 Honeycomb 兼容
所有大型文本属性都进行了截断处理,以适配 Honeycomb(追踪后端)的 64KB 属性限制:
const MAX_CONTENT_SIZE = 60 * 1024 // 60KB(Honeycomb limit 64KB,留安全余量) |
截断时会附加元数据:xxx_truncated: true, xxx_original_length: N。
6. Perfetto 追踪系统(独立于 OTel)
除了标准 OTel 追踪外,Claude Code 还实现了一套完全独立的 Perfetto 追踪系统(仅限内部用户)。
6.1 设计目的
Perfetto 提供的是本地可视化的全量追踪,而 OTel Traces 是上报到远端的采样追踪。它们是互补关系:
- OTel Traces:生产环境监控、问题排查、SLO 计量
- Perfetto:本地开发调试、性能 profiling、Agent 层次可视化
6.2 Chrome Trace Event 格式
Perfetto 输出标准的 Chrome Trace Event JSON,支持在 ui.perfetto.dev 或 chrome://tracing 中可视化:
type TraceEvent = { |
6.3 多 Agent 可视化
Perfetto 追踪的一大亮点是多 Agent 层次结构可视化:
// 每个 Agent 映射为独立的 "进程"(pid) |
在 Perfetto UI 中,每个子 Agent 显示为独立的进程轨道,其中的 API 调用、工具执行以瀑布图的形式排列,并且可以看到:
- Request Setup 子 Span(客户端初始化、重试等)
- First Token 子 Span(含 TTFT、ITPS)
- Sampling 子 Span(含采样速度 OTPS)
- Retry Attempt 子 Span(每次重试的起止时间)
6.4 API Call 的精细化子 Span
Perfetto 中的 API Call span 被分解为多个子阶段:
┌──────── API Call (B/E) ────────────────────────────┐ |
6.5 内存管理
长时间运行的 cron 会话可能产生大量事件,Perfetto 追踪有严格的内存控制:
const MAX_EVENTS = 100_000 // ~30MB |
过期 Span 也有 30 分钟 TTL 自动清理。
7. BigQuery 自定义 Metrics Exporter
7.1 设计动机
除了标准 OTLP 导出外,Claude Code 为 API 客户和企业用户实现了直连 BigQuery 的指标导出器。这是因为 Anthropic 需要在自己的数据仓库中分析产品使用指标。
7.2 导出条件
function isBigQueryMetricsEnabled() { |
7.3 数据格式
BigQuery Exporter 将 OTel 标准 ResourceMetrics 转换为精简的 JSON 格式:
{ |
7.4 组织级 Opt-out
BigQuery 导出尊重组织级别的 Metrics Opt-out 设置:
const metricsStatus = await checkMetricsEnabled() |
8. 代理与 mTLS 支持
OTLP Exporter 支持企业级网络配置:
function getOTLPExporterConfig() { |
9. 优雅关闭(Graceful Shutdown)
Agent 进程退出时的遥测数据保全是关键挑战。Claude Code 采用了多层保障:
9.1 关闭流程
const shutdownTelemetry = async () => { |
9.2 关键设计决策
- 先 flush 后 shutdown:
forceFlush()确保缓冲区数据发出,然后shutdown()释放资源 - 各 Provider 独立链式:Logger 的 flush 不阻塞 Tracer 的 shutdown(无瀑布效应)
- 超时竞赛:慢速的 OTLP 端点不会阻塞进程退出
- unref 定时器:超时用的 Timer 不阻止进程自然退出
9.3 数据安全 flush
// 登出或切换组织前,必须 flush 以防数据泄露 |
10. 工具调用(Tool Execution)的完整追踪生命周期
以一次工具调用为例,展示追踪在源码中的完整流转:
processTextPrompt.ts: |
11. 与其他 Agent 框架的对比
| 特性 | Claude Code | LangSmith/LangChain | OpenAI SDK |
|---|---|---|---|
| OTel 原生支持 | ✅ 全面使用 OTel SDK | ❌ 自定义协议 | 部分(自定义 trace) |
| 标准 OTLP 导出 | ✅ 三大信号全支持 | ❌ 专有后端 | ❌ |
| 自定义 Exporter | ✅ BigQuery + 1P | ✅ LangSmith | ❌ |
| 本地可视化 | ✅ Perfetto | ❌ | ❌ |
| Span 层次设计 | interaction→llm/tool→sub | chain→llm/tool | run→step |
| 隐私脱敏 | ✅ 多级 opt-in | 部分 | ❌ |
| 增量上下文追踪 | ✅ Hash-based delta | ❌ 全量 | ❌ |
| 企业级网络支持 | ✅ mTLS/Proxy/CA | ❌ | ❌ |
12. 设计启示总结
12.1 Agent 可观测性的关键挑战
- 长对话的上下文追踪:Agent 的对话历史持续增长,全量记录不现实。Claude Code 通过 Hash-based 增量追踪解决。
- 并行请求的归因:Agent 可能同时发起多个 LLM 请求(warmup、分类器、主线程)。Claude Code 通过显式传递 Span 引用而非依赖”最近的 Span”来解决。
- 工具执行的多阶段追踪:工具调用不是原子操作——它包括权限检查、用户等待、实际执行、Hook 运行。每个阶段都是独立的子 Span。
- CLI 工具的生命周期:进程随时可能退出,必须有超时竞赛和多层 flush 保障。
- 隐私与可观测性的平衡:默认脱敏 + 按需 opt-in + 按身份差异化展示。
12.2 架构决策精华
- 延迟加载一切:OTel 模块只在需要时加载,gRPC 模块按协议动态导入。
- WeakRef + TTL = 零泄漏:Span 管理既不阻止 GC,又有超时兜底。
- ALS 替代标准 Context:在 Agent 的嵌套执行模型中更自然。
- 双轨事件系统:内部分析与客户遥测严格隔离。
- Delta 时间粒度:CLI 短生命周期进程的最佳选择。
- console exporter 的 stdout 安全:在 stream-json 模式下自动剥离 console exporter,因为 stdout 是 SDK 消息通道。
12.3 值得借鉴的模式
- 属性工厂模式:
getTelemetryAttributes()集中管理所有公共维度 - 封装 Counter:
AttributedCounter自动注入基础属性 - 超时竞赛:
Promise.race([work, timeout])用于所有 I/O 操作 - 环境变量分层:
ANT_OTEL_* → OTEL_*的构建时/运行时映射 - Feature Gate 控制:GrowthBook 远程配置 + 环境变量覆盖 + 构建时死代码消除