# 遥测与可观测性完全解析

遥测系统不是 Claude Code 后加的监控——它是产品功能的一部分。超过 7,400 行代码实现了**五条独立的数据出站通道**（标准 OTLP、BigQuery 指标、Beta 详细追踪、Datadog 事件日志、1P First Party 事件上报），服务五个不同的数据消费方：企业用户对接自有可观测平台、Anthropic 产品团队分析使用模式、内部调试团队可视化 Agent 层级、运维团队实时监控、以及合规审计的事件归档。本章将解析这五条通道的架构、8 个 OTel Counter、Span 层级架构、隐私数据治理机制、以及 Perfetto Chrome Trace 格式的 Agent 可视化系统。

> **源码位置**：`src/utils/telemetry/`（4 个核心文件，3,363 行）、`src/services/analytics/`（9 个文件，4,040 行）

> 💡 **通俗理解**：遥测系统就像一辆装了五个不同摄像头的汽车——行车记录仪给车主看（OTLP 企业导出）、车载黑匣子给4S店分析（BigQuery 产品指标）、调试用的高清运动相机给工程师（Beta 详细追踪）、实时车况推送给远程监控中心（Datadog 事件日志）、以及完整行程日志存档到云端（1P 事件上报）。五个摄像头各拍各的，互不干扰，但都受一个"隐私开关"统一管控——你随时可以关掉不想被记录的部分。

### 🌍 行业背景：AI 工具的可观测性实践

可观测性（Observability）是分布式系统的标准实践，但 AI 编程工具对它的投入程度差异巨大：

- **LangChain/LangSmith**：这是 AI 可观测性的行业标杆。LangSmith 提供完整的 trace 可视化、token 用量追踪、延迟分析，Claude Code 的 Span 层级设计与 LangSmith 的 Run Tree 概念高度相似——都是将 AI 调用链建模为父子 Span 树。
- **Cursor**：内部遥测不对用户可见，没有提供 OTLP 企业导出能力。用户只能看到 token 用量的粗粒度统计。
- **Aider**：提供命令级日志和 token 统计，但没有结构化的 trace 系统，调试依赖文本日志。
- **GitHub Copilot**：服务端有完整的遥测（用于改进模型），但客户端可观测性有限，企业管理员通过 GitHub Admin Console 查看使用统计。
- **OpenTelemetry (OTel)**：Claude Code 选择 OTel 作为企业遥测标准，这是正确的行业选择——OTel 已成为 CNCF 毕业项目，被 Datadog、Grafana、Honeycomb 等主流平台支持。Codex（OpenAI）目前不提供 OTel 集成。
- **Weights & Biases / Helicone**：AI 领域的专用可观测平台，提供 prompt 追踪、成本分析等功能，但需要额外集成。

Claude Code 的五条并行数据通道（OTLP + BigQuery + Beta Tracing + Datadog + 1P）在 AI 编程工具中属于相当复杂的实现。OTel 集成在 2025 年已是企业级软件的基础要求，Claude Code 做到了这一点说明它是"企业就绪"的。Perfetto Chrome Trace 的选择也很务实——复用成熟的性能分析工具链而非自建可视化。值得注意的是，这五条通道更可能是多次迭代有机生长的结果，而非一次性的统一架构设计——每条路径有独立的序列化格式、批处理策略、重试逻辑和采样机制。

---

## 概述

Claude Code 的遥测系统不是后加的监控——它是**产品功能的一部分**。系统在超过 7,400 行代码中实现了**五条独立的数据出站通道**，8 个 OTel Counter，一套精心设计的 Span 层级（interaction→llm_request→tool→execution），一套基于类型系统的隐私数据治理机制，以及 Perfetto Chrome Trace 格式的 Agent 可视化。五条通道各有其数据消费方：

| 通道 | 消费方 | 代码量 |
|------|--------|--------|
| OTLP exporters | 企业用户（对接自有 Datadog/Grafana/Honeycomb） | `instrumentation.ts` 825 行 |
| BigQuery metrics | Anthropic 产品团队（使用模式分析） | `instrumentation.ts` 内含 |
| Beta detailed tracing | 内部调试团队（Span 级调试追踪） | `betaSessionTracing.ts` 491 行 |
| Datadog 事件日志 | 运维团队（实时监控与告警） | `datadog.ts` 307 行 |
| 1P First Party 事件上报 | 合规审计 + 产品分析（protobuf 格式事件归档） | `firstPartyEventLoggingExporter.ts` 806 行 + `firstPartyEventLogger.ts` 449 行 |

---

> **[图表预留 3.8-A]**：五条数据出站通道图 — OTLP exporters / BigQuery metrics / Beta detailed tracing / Datadog event logs / 1P event reporting 的并行架构，标注各通道的启用条件、数据格式、传输协议和隐私级别

> **[图表预留 3.8-B]**：Span 层级图 — interaction → llm_request / tool → blocked_on_user / execution 的父子关系

---

## 1. 初始化与五条数据出站通道

### 1.1 启动流程

`initializeTelemetry()`（`instrumentation.ts:421-701`）是遥测的入口，按条件激活前三条路径。`initializeAnalyticsSink()`（`sink.ts`）在应用启动时激活后两条路径。五条通道的完整对比：

| 通道 | 启用条件 | 数据格式 | 传输协议 | 批处理策略 | 失败处理 | 隐私级别 |
|------|---------|---------|---------|-----------|---------|---------|
| OTLP exporters | `CLAUDE_CODE_ENABLE_TELEMETRY` 环境变量 | OTel spans/metrics/logs | OTLP HTTP | Metrics 60s, Logs 5s, Traces 5s | OTel SDK 标准重试 | 用户控制 |
| BigQuery metrics | 1P API（非 Bedrock/Vertex/**Foundry**）或 C4E 或 Teams | OTel metrics | OTLP（Anthropic 1P ingest endpoint） | Periodic reader · 5 分钟 | — | Anthropic 内部 |
| Beta detailed tracing | `ENABLE_BETA_TRACING_DETAILED=1` + `BETA_TRACING_ENDPOINT` + org 白名单 | OTel spans + 自定义属性 | OTLP HTTP（默认指向 Honeycomb，但 endpoint 可配置） | BatchSpanProcessor | OTel SDK 标准 | ant-only thinking |
| Datadog 事件日志 | `tengu_log_datadog_events` GrowthBook gate + 非 killswitch | JSON logs | HTTPS POST（`DATADOG_LOGS_ENDPOINT`） | 每 15s 或 100 条批量 flush | fire-and-forget（失败仅 logError） | **剥离 `_PROTO_*` 字段** |
| 1P First Party 事件上报 | 非 `isAnalyticsDisabled()` + 非 killswitch | Protobuf（`ClaudeCodeInternalEvent`） | HTTPS POST（`/api/event_logging/batch`） | OTel BatchLogRecordProcessor（5s 或 200 条） | **持久化重试**（JSONL 本地存储 + 二次方退避） | **接收完整 `_PROTO_*` 字段** |

这张表揭示了一个关键的架构特征：**五条通道各自为政**。每条路径有独立的序列化格式、批处理策略、重试逻辑和采样机制——这更像是多次迭代叠加的结果，而非一次性的统一管道 + 多 sink 设计。可能的原因是：(a) 历史上各通道独立演化，(b) 不同通道的可靠性要求不同（1P 有持久化重试，Datadog 是 fire-and-forget），(c) 不同通道的数据敏感级别不同（`_PROTO_*` 字段只走 1P）。

### 1.2 BigQuery 指标启用条件

`isBigQueryMetricsEnabled()`（`instrumentation.ts:336-347`）——只对以下用户启用：
- 1P API 客户（**排除** Bedrock、Vertex 和 **Foundry** 用户——`config.ts:24` 的 `isAnalyticsDisabled()` 明确排除了 `CLAUDE_CODE_USE_FOUNDRY`）
- Claude for Enterprise (C4E) 用户
- Claude for Teams 用户

注意 Foundry 是 Anthropic 的企业部署方案，排除它意味着通过 Foundry 部署的企业用户的产品行为对 Anthropic 产品团队是不可见的——这是一个有意的数据隔离边界。

> **`isBigQueryMetricsEnabled()` vs `isAnalyticsDisabled()` 不是同义词**：前者是正向开关（"是否要走 BigQuery 这条指标通道"），决定是否初始化 `PeriodicExportingMetricReader`；后者是负向总开关（"是否要关闭所有面向 Anthropic 的遥测通道"），决定 Datadog + 1P + GrowthBook 的整体关停。两者共享 Bedrock/Vertex/Foundry 的排除逻辑，但覆盖通道不同：前者只影响 BQ 指标，后者影响所有对 Anthropic 上报的数据。

### 1.3 OTLP 导出器配置

`getOTLPExporterConfig()`（`instrumentation.ts:749-825`）支持：
- mTLS（双向 TLS）
- CA 证书链
- HTTP 代理
- 动态 Headers（通过 `otelHeadersHelper` 脚本生成）

### 1.4 Datadog 事件日志通道

`datadog.ts`（307 行）实现了一条独立的事件日志管道，通过 `DATADOG_LOGS_ENDPOINT`（`https://http-intake.logs.us5.datadoghq.com/api/v2/logs`）批量上报事件。

**关键设计细节**：

- **事件白名单**：`DATADOG_ALLOWED_EVENTS` 是一个硬编码的 Set，包含约 30 种 `tengu_*` 前缀事件（如 `tengu_init`、`tengu_api_error`、`tengu_exit`、`tengu_tool_use_success` 等）和 `chrome_bridge_*` 系列事件。只有白名单内的事件才会被发送——这既是安全边界也是成本控制。
- **批量发送**：每 15 秒或 100 条消息 flush 一次（`DEFAULT_FLUSH_INTERVAL_MS = 15000`，`MAX_BATCH_SIZE = 100`）。
- **隐私隔离**：Datadog 是通用访问后端（general-access backend），所有发往 Datadog 的事件在 `sink.ts` 中经过 `stripProtoFields()` 处理——`_PROTO_*` 前缀的 PII 标记字段会被完整剥离。
- **Client Token 硬编码**：`DATADOG_CLIENT_TOKEN` 直接写在源码中。这是 client token（不是 API key），安全风险有限，但源码一旦进入公开渠道就会暴露这个值。
- **GrowthBook Gate 控制**：通过 `tengu_log_datadog_events` 远程开关控制是否启用。

### 1.5 1P First Party 事件上报通道

`firstPartyEventLoggingExporter.ts`（806 行）+ `firstPartyEventLogger.ts`（449 行）实现了最复杂的数据上报通道。

**可靠性机制**——这是五条通道中唯一有完整故障恢复能力的：

1. **Append-only 失败事件存储**：发送失败的事件以 JSONL 格式追加写入本地文件（`~/.claude/telemetry/1p_failed_events.<uuid>.jsonl`），进程重启后可重新读取。
2. **二次方退避重试**（quadratic backoff）：失败后的重试间隔按 `baseDelay * attempt^2` 增长，避免对后端造成冲击。
3. **成功时立即重试队列**：当一次 export 成功，说明 endpoint 健康，立即尝试发送之前失败的事件。
4. **401 认证降级**：遇到认证错误时，会尝试以无认证方式重试——这是一个防御性策略，确保在 OAuth token 过期的窗口期内不丢失事件。
5. **大批量分块**：超大事件集会被分割为小批次发送，避免单次请求体过大。

**数据格式**：1P 通道使用 Protobuf 格式（`ClaudeCodeInternalEvent`），事件通过 `to1PEventFormat()` 转换后发送到 `/api/event_logging/batch` 端点。

### 1.6 Sink 统一调度（sink.ts）

`sink.ts`（114 行）是 Datadog 和 1P 这两条通道的统一调度层。它的核心逻辑：

```
logEventImpl(eventName, metadata)
  ├── shouldSampleEvent(eventName) → 采样决策
  ├── shouldTrackDatadog() → Datadog 通道
  │   └── trackDatadogEvent(eventName, stripProtoFields(metadata))  // 剥离 PII
  └── logEventTo1P(eventName, metadataWithSampleRate)  // 保留完整 _PROTO_* 字段
```

注意第 66-67 行的关键注释：Datadog 是 "general-access backend"，所以通过 `stripProtoFields()` 剥离 PII 标记字段；1P 接收完整载荷，由 exporter 自行将 `_PROTO_*` 键路由到 protobuf 的特权列。

### 1.7 事件采样系统

`firstPartyEventLogger.ts:57-80` 实现了通过 GrowthBook 远程控制的动态采样：

- **配置名**：`tengu_event_sampling_config`——一个 JSON 对象，每种事件类型映射一个 `sample_rate`（0-1 之间的数字）。
- **未配置的事件按 100% 采样**——默认全量记录。
- **采样率为 0 的事件被完全丢弃**——这是一个远程 killswitch 的变种。
- **采样率附加到 metadata**：被采样的事件会在 metadata 中附加 `sample_rate` 字段，下游分析时可以反推总量。

这意味着 Anthropic 可以**在不发版本的情况下**调整遥测粒度——在成本和信息完整性之间动态平衡。这种远程可控的采样架构比静态的采样率有更强的运维灵活性。

### 1.8 Sink Killswitch 机制

`sinkKillswitch.ts`（25 行）实现了一个紧急关闭机制，通过 GrowthBook 的 `tengu_frond_boric` 动态配置远程关闭特定数据通道。

```typescript
// 配置名用混淆命名——刻意避免用户发现和滥用
const SINK_KILLSWITCH_CONFIG_NAME = 'tengu_frond_boric'
export type SinkName = 'datadog' | 'firstParty'

export function isSinkKilled(sink: SinkName): boolean {
  const config = getDynamicConfig_CACHED_MAY_BE_STALE<
    Partial<Record<SinkName, boolean>>
  >(SINK_KILLSWITCH_CONFIG_NAME, {})
  return config?.[sink] === true
}
```

**设计要点**：

- **Fail-open 策略**：配置缺失或格式错误时，默认值为空对象 `{}`，意味着所有 sink 保持开启。这是一个有意识的选择——宁可多发数据也不默默丢数据。
- **混淆命名**：`tengu_frond_boric` 这个名字没有任何语义含义，刻意避免被终端用户在 GrowthBook 配置中发现并手动禁用。
- **用途**：这是一个事故响应机制——当某条数据管道出问题时（比如 Datadog endpoint 挂掉导致客户端超时），可以在不重启客户端的情况下立即切断该通道。

## 2. 八个 Counter

`state.ts:952-989` 定义了系统的核心指标：

| Counter | 指标名 | 单位 | 维度 |
|---------|--------|------|------|
| `sessionCounter` | `claude_code.session.count` | count | — |
| `locCounter` | `claude_code.lines_of_code.count` | count | `type`: added/removed |
| `prCounter` | `claude_code.pull_request.count` | count | — |
| `commitCounter` | `claude_code.commit.count` | count | — |
| `costCounter` | `claude_code.cost.usage` | USD | — |
| `tokenCounter` | `claude_code.token.usage` | tokens | — |
| `codeEditToolDecisionCounter` | `claude_code.code_edit_tool.decision` | count | `decision`: accept/reject, `tool`: Edit/Write/NotebookEdit |
| `activeTimeCounter` | `claude_code.active_time.total` | seconds | — |

这些 Counter 的维度选择揭示了产品关注点：**代码编辑工具的接受/拒绝率**有独立 Counter（`codeEditToolDecisionCounter`），说明团队在密切监控 AI 修改代码的质量。

## 3. Span 层级架构

### 3.1 标准 Span

`sessionTracing.ts`（928 行）定义了三层 Span 层级：

```
claude_code.interaction（根 Span）
├── user_prompt（属性，隐私控制时不记录）
├── interaction.sequence（单调递增计数器）
│
├── claude_code.llm_request（父：interaction via otelContext）
│   ├── model, speed ('fast'/'normal')
│   ├── query_source（Agent 名称）
│   ├── input_tokens, output_tokens
│   ├── cache_read_tokens, cache_creation_tokens
│   ├── ttft_ms（首 token 延迟）
│   └── [Beta] response.model_output, response.thinking_output
│
├── claude_code.tool（父：interaction）
│   ├── tool_name
│   ├── claude_code.tool.blocked_on_user
│   │   ├── decision, source
│   │   └── duration_ms
│   └── claude_code.tool.execution
│       ├── success, error
│       └── duration_ms
│
└── claude_code.hook（Beta only，父：tool 或 interaction）
    ├── hook_event, hook_name
    └── num_success, num_blocking, num_non_blocking_error
```

### 3.2 WeakRef + AsyncLocalStorage 的 Span 生命周期管理

`sessionTracing.ts:66-75` 实现了一套精心设计的内存管理策略——在 OTel 的标准用法中非常少见：

```
activeSpans: Map<string, WeakRef<SpanContext>>   // 弱引用
strongSpans: Map<string, SpanContext>             // 强引用
interactionContext: AsyncLocalStorage<SpanContext> // ALS 作为 GC root
toolContext: AsyncLocalStorage<SpanContext>        // ALS 作为 GC root
```

> **术语消歧**：这里的 `SpanContext` 是 `sessionTracing.ts` 内部定义的 TypeScript 类型名（包装了 OTel Span 实例 + 自定义的 session 元数据），**不是** OTel 规范里 `SpanContext`（只含 `{traceId, spanId, traceFlags, traceState, isRemote}` 的不可变标识符）。二者同名但不同义——Claude Code 在这里用的是"业务侧的 Span 上下文对象"，而不是分布式追踪传播协议里的"链路 ID 快照"。

**设计逻辑**：
- **interaction 和 tool Span** 存储在 `AsyncLocalStorage`（ALS）中。ALS 是 GC root——只要当前异步上下文存活，SpanContext 就不会被回收。`activeSpans` 中只持有 `WeakRef`，当 ALS 被清除（`enterWith(undefined)`）且没有其他代码持有引用时，GC 可以回收 SpanContext，WeakRef 自动失效。
- **LLM request、blocked-on-user、tool execution、hook Span** 不在 ALS 中管理，所以需要 `strongSpans` Map 持有强引用——否则 GC 可能在 `endLLMRequestSpan()` 调用之前就回收 SpanContext。

这是一种 **GC-aware 的 Span 管理模式**：利用 JavaScript 的垃圾回收机制自动清理不再需要的 Span，而不是手动跟踪所有 Span 的生命周期。对于 Claude Code 这样长时运行的 CLI 进程（特别是 cron 驱动的 session 可能运行数天），这种设计显著降低了内存泄漏的风险。

### 3.3 其他关键设计决策

- **SPAN_TTL_MS = 30 分钟**（`sessionTracing.ts:79`）：孤儿 Span 每 60 秒清理一次——这是 WeakRef 之外的第二道防线
- **`isEnhancedTelemetryEnabled()`**（`sessionTracing.ts:126-143`）优先级：env override > ant build > GrowthBook cached gate
- **并行请求处理**：`endLLMRequestSpan()` 通过显式 span 参数处理并行请求（`sessionTracing.ts:353-401`）

> 📚 **课程关联（分布式系统）**：Span 层级架构直接对应分布式系统课程中的**分布式追踪**（Distributed Tracing）概念。interaction→llm_request→tool→execution 的父子关系本质上是一棵调用树（call tree），与 Google Dapper 论文（2010）提出的 Trace/Span 模型一脉相承。OTel 的 Context Propagation 机制——通过 `otelContext` 将父 Span ID 传递给子 Span——等价于分布式系统中的因果关系追踪（causality tracking）。

## 4. 隐私与数据治理系统

遥测系统最容易犯的错误是无意中记录了用户代码或文件路径。Claude Code 为此建立了一套多层次的隐私治理机制——从 TypeScript 类型系统到运行时字段路由，层层防御。

### 4.1 类型系统编码的隐私审查——`AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS`

`analytics/index.ts:19` 定义了一个 TypeScript `never` 类型：

```typescript
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never
```

这不是运行时检查——它是**开发时强制意识**。任何开发者想在 analytics 事件中记录字符串型元数据时，必须显式写下 `as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS` 这个 assert。这个冗长到刻意不便的类型名迫使开发者在每次使用时停下来思考："我要记录的这个字符串确实不包含代码或文件路径吗？"

> 💡 **通俗理解**：这就像银行柜台取大额现金时需要你手写"我确认这笔取款是本人意愿"——不是真的能阻止你取钱，而是让你在操作前多想一秒。

### 4.2 `_PROTO_*` 字段路由——PII 数据的特权通道

同一模块还定义了第二个类型标记：

```typescript
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never
```

带有 `_PROTO_` 前缀的 metadata 键是 PII（个人可识别信息）标记字段。这些字段的路由规则是：

1. **1P 通道**（`firstPartyEventLoggingExporter.ts`）：接收完整载荷，exporter 将 `_PROTO_*` 键提升（hoist）到 protobuf 顶层字段，这些字段在 BigQuery 中有特权访问控制。
2. **Datadog 通道**（`sink.ts`）：在发送前调用 `stripProtoFields()` **完整剥离**所有 `_PROTO_*` 键——Datadog 永远看不到未脱敏的 PII。
3. **其他通道**：不经过 sink 调度的通道（OTLP、Beta tracing）有各自独立的隐私控制（`isEnhancedTelemetryEnabled()`、ant-only 限制等）。

`stripProtoFields()` 函数（`index.ts:45-58`）的实现有一个优化细节：当 payload 中没有 `_PROTO_*` 键时，直接返回原引用（不做拷贝），避免在高频事件路径上产生不必要的对象分配。

### 4.3 三级隐私控制矩阵

`privacyLevel.ts` 定义了三级隐私等级，与各通道的启用状态形成一个控制矩阵：

| 隐私级别 | OTLP | BigQuery | Beta Tracing | Datadog | 1P |
|----------|------|----------|-------------|---------|-----|
| `default`（默认） | 用户启用时开 | 条件启用 | 条件启用 | Gate 控制 | 开 |
| `no-telemetry` | 关 | 关 | 关 | 关 | 关 |
| `essential-traffic` | 关 | 关 | 关 | 关 | 关 |

注意需要区分两个**不同的关闭开关**，它们作用在不同层：

1. **`privacyLevel`**（用户隐私级别）—— 矩阵中的 `no-telemetry` 确实会关闭所有 5 个通道（包括 OTLP）。这是用户对"一切遥测"的最高级否决。
2. **`DISABLE_TELEMETRY` 环境变量**（仅影响 Anthropic 消费的数据）—— 只关闭 Datadog + 1P + GrowthBook（由 `isAnalyticsDisabled()` 判断），**不关闭 OTLP**。OTLP 是用户通过 `CLAUDE_CODE_ENABLE_TELEMETRY` 主动启用的企业导出通道，不受 Anthropic 隐私开关约束。

一句话区分：**`privacyLevel=no-telemetry` 是全关；`DISABLE_TELEMETRY` 只关 Anthropic 自己收的数据**。"谁的遥测谁控制"的哲学体现在第二层——用户自己开的 OTLP 由用户自己控制。

### 4.4 `isAnalyticsDisabled()` 的多条件判断

`config.ts` 的 `isAnalyticsDisabled()` 在以下任一条件满足时返回 true：
- `NODE_ENV === 'test'`（测试环境）
- `CLAUDE_CODE_USE_BEDROCK`（AWS Bedrock 部署）
- `CLAUDE_CODE_USE_VERTEX`（Google Vertex 部署）
- `CLAUDE_CODE_USE_FOUNDRY`（Anthropic Foundry 企业部署）
- `isTelemetryDisabled()`（用户主动禁用遥测）

这意味着所有第三方云部署（Bedrock/Vertex/Foundry）的用户数据**不会**流入 Anthropic 的 Datadog 和 1P 通道——这既是隐私保障，也是合规边界。

## 5. Beta 详细追踪——系统提示词哈希

### 5.1 启用条件

`isBetaTracingEnabled()`（`betaSessionTracing.ts:78-98`）需要三个条件同时满足：
1. `ENABLE_BETA_TRACING_DETAILED=1`
2. `BETA_TRACING_ENDPOINT` 设置
3. SDK/headless 模式 **或** 组织在 `tengu_trace_lantern` GrowthBook gate 的白名单中

**关于导出目标的准确说法**：源码中 Beta tracing 使用的是标准 `@opentelemetry/exporter-trace-otlp-http`，endpoint 通过 `BETA_TRACING_ENDPOINT` 环境变量配置。60KB 截断注释确实提到了 Honeycomb 限制，但准确地说应该是"默认配置指向 Honeycomb，但实际是标准 OTLP 协议"——endpoint 可以指向任何兼容 OTLP 的后端。

### 5.2 系统提示词哈希去重

`betaSessionTracing.ts:129-131`：

```typescript
hashSystemPrompt() → 'sp_<12字符SHA256>'
```

同一 session 内，相同的系统提示词只记录一次（通过 `seenHashes` Set 跟踪）。这避免了每轮都传输巨大的系统提示词——对长 session 的带宽节省显著。这本质上是一种 memoization 模式——"同一个字符串不重复发送"。

### 5.3 新上下文增量追踪

`betaSessionTracing.ts:334-399`：Beta tracing 不是每次都传输完整上下文，而是计算**增量**：

- 按 `querySource`（Agent 名称）独立追踪 `lastReportedMessageHash`
- 每次只传输自上次报告以来新增的用户消息
- 将 `<system-reminder>` 标签的内容从常规上下文中分离出来

### 5.4 隐私规则

| 数据类型 | 可见性 |
|----------|--------|
| 系统提示词（哈希 + 预览）| 所有用户 |
| 模型输出 | 所有用户 |
| Thinking 输出 | **ant-only** |
| 工具 Schema | 所有用户（哈希去重） |

需要注意：`user_prompt` 在 Beta tracing 启用时**总是被记录**（通过 `addBetaInteractionAttributes()`），只是做了 60KB 截断。真正的隐私控制是 `isEnhancedTelemetryEnabled()` 决定是否启用标准增强遥测，以及 thinking output 的 `ant-only` 限制。

### 5.5 内容截断

`truncateContent()`（`betaSessionTracing.ts:103-117`）——60KB 上限，注释说明这是 Honeycomb 的安全边界。

### 5.6 Session Compaction 联动

`clearBetaTracingState()`（`betaSessionTracing.ts:65-68`）在 session compaction 后调用——因为 compaction 会重写消息历史，之前的哈希增量追踪需要重置。

## 6. Perfetto Chrome Trace（ant-only）——1,120 行的 Agent 可视化系统

`perfettoTracing.ts`（1,120 行）不是一个简单的 trace 格式转换——它是一个完整的 Agent 运行时可视化系统，实现了 Chrome Trace Event 格式，可以直接在 `ui.perfetto.dev` 或 Chrome 的 `chrome://tracing` 中查看。

### 6.1 启用方式

通过环境变量控制（`feature('PERFETTO_TRACE')` 在非 ant build 中被消除）：
- `CLAUDE_CODE_PERFETTO_TRACE=1`：启用，trace 文件写入 `~/.claude/traces/trace-<session-id>.json`
- `CLAUDE_CODE_PERFETTO_TRACE=<path>`：启用，trace 文件写入指定路径
- `CLAUDE_CODE_PERFETTO_WRITE_INTERVAL_S=<秒数>`：周期性写入（默认仅在退出时写入）

### 6.2 Agent 层级映射——进程/线程 ID 模型

这是 Perfetto 实现中最精妙的设计。Chrome Trace 格式原本是为多进程浏览器设计的（每个 tab 一个进程），Claude Code 巧妙地将 Agent 层级映射到这个模型上：

- **进程 ID（pid）**：主 Agent 使用 pid=1，每个子 Agent（swarm 模式下的 teammate）分配递增的进程 ID——通过 `getProcessIdForAgent()` 管理。
- **线程 ID（tid）**：使用 Agent 名称的 DJB2 哈希值作为线程 ID（`stringToNumericHash(agentName)`），确保同名 Agent 的事件在同一"线程轨道"上对齐。
- **Metadata 事件**：为每个 Agent 生成 `process_name` 和 `thread_name` metadata 事件，Perfetto UI 用这些来标记轨道。

在 team swarm 模式下，多个 Agent 的并行执行在 Perfetto 时间轴上呈现为**多进程并行**——可以直观看到哪些 Agent 在同时工作、各自在做什么、调用了哪些工具。

### 6.3 100,000 事件上限与 Eviction 策略

`MAX_EVENTS = 100_000`（`perfettoTracing.ts:106`）——这个看似随意的数字背后有真实的运维考量。源码注释解释了原因：

> "Cron-driven sessions run for days; 22 push sites x many turns would otherwise grow unboundedly"

这揭示了 Claude Code 的一个重要使用场景：**Cron 驱动的长时运行 session**，可能在 22 个代码仓库的 push 事件上持续运行数天。按每事件约 300 字节计算，100K 事件上限约为 30MB——足够调试任何场景。

**Eviction 策略**：当事件数达到上限时，丢弃最老的一半事件（amortized O(1)）。注意 metadata 事件（进程/线程名称）被单独存储在 `metadataEvents` 数组中，**不受 eviction 影响**——Perfetto UI 需要这些来正确标记轨道。

### 6.4 Stale Span 清理

与 `sessionTracing.ts` 中的 SPAN_TTL 机制类似，Perfetto 也有自己的 stale span 清理：

- `STALE_SPAN_TTL_MS = 30 分钟`
- 每 60 秒检查一次 `pendingSpans`
- 过期的 span 会被自动结束并标记 `evicted: true`——在 Perfetto 中仍然可见，但有明确标记

### 6.5 周期性写入

`CLAUDE_CODE_PERFETTO_WRITE_INTERVAL_S` 环境变量允许设置周期性写入间隔。这对长时运行场景至关重要——如果只在退出时写入，进程崩溃会丢失所有 trace 数据。周期性写入每次写的是**完整快照**（不是增量），这意味着每次写入都包含 `metadataEvents + events` 的全部内容。

### 6.6 记录的事件类型

Perfetto trace 记录以下事件类型，每种映射为不同的 Chrome Trace Event phase：

- **API 请求**：TTFT（首 token 延迟）、TTLT（末 token 延迟）、prompt 长度、cache stats、model ID、speculative 标记
- **工具执行**：工具名称、执行时长、token 使用量
- **用户等待**：用户输入等待时间（blocked-on-user）
- **Agent 层级**：父子 Agent 关系、Agent 生命周期

即使是 ant-only 功能，Perfetto 展示的**多 Agent 可视化思路**对从业者有极高参考价值——将 Agent 层级映射到 Chrome Trace 的进程/线程模型，避免了自建可视化前端的工程成本，直接复用 `ui.perfetto.dev` 和 Chrome DevTools 的成熟工具链。需要说明："零成本"是相对的——可视化**前端**是零成本（复用现成工具），但**生成端**本身是 1,120 行代码（`perfettoTracing.ts`）的实现投入，包括进程/线程 ID 分配、stale span 清理、100K 事件 eviction 等。

## 7. GrowthBook 远程控制面

GrowthBook 不仅是 feature gate 平台——它是整个遥测系统的**远程控制面**。以下是遥测相关的全部 GrowthBook 配置：

| 配置名 | 类型 | 功能 |
|--------|------|------|
| `tengu_trace_lantern` | Feature Gate | Beta tracing 组织白名单 |
| enhanced telemetry gate | Feature Gate（cached） | 标准增强遥测开关 |
| `tengu_log_datadog_events` | Feature Gate | Datadog 事件日志开关 |
| `tengu_event_sampling_config` | Dynamic Config（JSON） | 每事件类型的采样率配置 |
| `tengu_frond_boric` | Dynamic Config（JSON） | Sink killswitch（混淆命名） |
| `tengu_1p_event_batch_config` | Dynamic Config（JSON） | 1P 批量发送参数（延迟、批大小、重试次数等） |

**降级策略**：本章出现的 GrowthBook 查询大多使用 `_CACHED_MAY_BE_STALE` 变体（即"允许读过期缓存值"的非阻塞路径）。另外还有 `_CACHED_OR_BLOCKING` 变体会在缓存缺失时同步等待一次远程加载，以及更少见的显式 refresh API——遥测路径上几乎全都走允许 stale 的非阻塞版本，因为遥测代码本身不能阻塞主流程。当 GrowthBook 不可达时：
- Feature Gate：使用上次成功获取的缓存值
- Dynamic Config：使用代码中的默认值（`{}` 或具体默认对象）
- **Fail-open 设计**：killswitch 缺失时默认所有 sink 保持开启；采样配置缺失时默认全量记录

这意味着 GrowthBook 的可用性不会成为遥测系统的单点故障——但长时间不可达后，缓存值可能严重过时。

## 8. 设计取舍与评价

**优秀**：
1. **类型系统编码的隐私审查**——`AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS` 类型标记和 `_PROTO_*` 字段路由，将隐私合规从运行时检查提升到开发时强制意识，这是整个遥测系统中最能体现安全工程思维的设计
2. **1P 事件的完整故障恢复**——持久化存储 + 二次方退避 + 认证降级，远比 OTel 的标准 `BatchSpanProcessor` 丰富
3. **远程可控的事件采样**——通过 GrowthBook 的 `tengu_event_sampling_config` 在不发版本的情况下动态调整遥测粒度
4. **Sink Killswitch 机制**——事故响应级别的防御性设计，混淆命名防止滥用
5. `codeEditToolDecisionCounter` 的存在说明团队在数据驱动地优化 AI 代码编辑的质量
6. Perfetto 将 Agent 层级映射为 Chrome Trace 的进程/线程模型——零成本复用成熟的性能分析工具链
7. **WeakRef + AsyncLocalStorage 的 Span 生命周期管理**——GC-aware 的设计降低了长时运行场景下的内存泄漏风险
8. Session compaction 联动确保了追踪状态的一致性
9. OTLP 配置支持 mTLS + 动态 Headers，满足企业级安全要求

**代价**：
1. **五条独立管道的维护成本**——每条路径有独立的序列化格式、批处理策略、重试逻辑、采样机制，没有统一的 event bus + 多 sink 架构。可能原因：历史演化、可靠性要求差异、数据敏感级别差异
2. 30 分钟 SPAN_TTL 意味着超长 session 的早期 Span 会被清理——可能丢失调试信息
3. Beta tracing 的 thinking 输出限制 ant-only 说明隐私边界仍在谨慎评估中
4. 60KB 截断是对 Honeycomb 系统限制的妥协——特别大的工具输出会被截断
5. BigQuery 指标不包含 Bedrock/Vertex/Foundry 用户——这些用户的产品行为是盲区
6. Perfetto 仅 ant-only 意味着企业用户无法获得 Agent 层级可视化——错失了一个高价值的调试功能
7. **GrowthBook 作为远程配置中心的隐性依赖**——Beta tracing 白名单、增强遥测 gate、事件采样率、sink killswitch 都依赖 GrowthBook。虽然有 `_CACHED_MAY_BE_STALE` 降级策略，但长时间不可达后缓存失效的行为未明确定义

---

