[控场AI]
· 17 分钟阅读· 8,516 字

Claude Code 已内置 OpenTelemetry:Grafana 追踪成本与模型

Claude Code 已内置 OpenTelemetry:Grafana 追踪成本与模型

用 OpenTelemetry 与 Grafana 拆解 Claude Code 的成本、模型与调用来源。

文章介绍如何为 Claude Code 搭建一套本地成本可观测流水线。Claude Code 内置 OpenTelemetry 导出器,设置 CLAUDE_CODE_ENABLE_TELEMETRY=1 后即可输出成本、Token、会话、代码编辑等指标和事件,默认不采集提示词与代码内容。作者用 Docker Compose 定义四个文件,串起 OTel Collector、Prometheus 与 Grafana,并通过环境变量让 Claude Code 把 OTLP 数据推送到本地 Collector。Grafana 中三个 Bar gauge 面板分别按用户、模型和 query_source 拆解成本,另有缓存命中率查询。文章还详列了多项陷阱:成本为估算值、API key 场景缺少身份、session.id 膨胀基数、短会话少计、桌面端服务名不同,并指出这些指标无法衡量工作质量。

团队使用 Claude Code 的支出,在账单上通常只显示为一个总数。随后就会出现一连串无法回答的问题:上个月支出飙升是哪个团队造成的?是昂贵模型还是便宜模型?子智能体是否在悄悄消耗一半预算?答案其实就藏在数据里,只是默认没打开。

Claude Code 内置了 OpenTelemetry 导出器,只要设置一个环境变量 CLAUDE_CODE_ENABLE_TELEMETRY=1,就能把每次 API 请求的成本、Token 数,连同模型、用户、会话等标签一起发出来。本文带你搭一套本地流水线:Claude Code 将数据推给 OpenTelemetry Collector,Prometheus 抓取,Grafana 展示,再用三个面板回答上面那些问题。

Claude Code 遥测数据流向示意

Claude Code 到底导出了什么

对成本仪表盘而言,关键信号有两类:指标(metrics)和事件(events)。指标经由 metrics exporter 发送,事件经由 logs exporter 发送。

根据官方监控文档,主要指标包括:

  • claude_code.cost.usage:每次 API 请求后累加的成本(美元),附带 model、query_source(main / subagent / auxiliary)、effort,以及当前技能、插件或子智能体的归属信息。
  • claude_code.token.usage:按 type 拆分为 input、output、cacheRead、cacheCreation。
  • claude_code.session.count、claude_code.lines_of_code.count、claude_code.commit.count、claude_code.pull_request.count、claude_code.active_time.total。
  • claude_code.code_edit_tool.decision:统计 Edit、Write、NotebookEdit 的接受与拒绝决策,并附带文件语言。

事件包括 user_prompt、api_request(含 cost_usd、duration_ms 和每次调用的 Token 数)、api_error、tool_result 与 tool_decision。

每个指标和事件都携带 session.id 和 user.id(随机安装标识)。使用 Claude 账户登录时,还会填充 organization.id、user.account_uuid 和 user.email;文档明确说明邮箱只会发送到你配置的端点,不会发给 Anthropic。

默认配置偏向隐私保护:不收集提示词文本,只收集长度(除非设置 OTEL_LOG_USER_PROMPTS=1);Bash 命令和 MCP 工具参数默认不记录(除非设置 OTEL_LOG_TOOL_DETAILS=1);原始文件内容和代码片段也不在指标或事件中。做成本仪表盘时,这些开关全部保持关闭即可。

OpenTelemetry(OTel)是 CNCF 旗下的可观测性标准,统一了指标(metrics)、日志(logs)和追踪(traces)三类信号的采集与传输协议 OTLP。它的架构由 SDK(在应用进程内产生数据)和 Collector(独立的接收、处理、导出进程)组成,Collector 通过 receiver、processor、exporter 三段式管道把数据转换后送往任意后端。Claude Code 内置的正是 OTel SDK,因此它输出的数据格式并非私有,任何兼容 OTLP 的后端——Prometheus、Datadog、Honeycomb 等——都能直接接收。理解这一点很关键:本文选择 Prometheus + Grafana 只是其中一种落地方式,指标本身并不绑定于这套栈。

四文件搭起可观测栈

新建一个目录,放入四个文件。Collector 接收来自 Claude Code 的 OTLP 数据,在 8889 端口以 Prometheus 格式暴露指标;Prometheus 抓取;Grafana 读取 Prometheus。

docker-compose.yml 把镜像版本固定为当前发布版:

services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.162.0
    command: ["--config=/etc/otelcol/config.yaml"]
    volumes:
      - ./otel-collector.yaml:/etc/otelcol/config.yaml:ro
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
      - "8889:8889"   # Prometheus 抓取端点

  prometheus:
    image: prom/prometheus:v3.15.0
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:13.2.4
    environment:
      GF_SECURITY_ADMIN_PASSWORD: change-me
    volumes:
      - ./grafana:/etc/grafana/provisioning/datasources:ro
    ports:
      - "3000:3000"

otel-collector.yaml 中,指标走 Prometheus exporter,事件先送到 debug exporter,方便在容器日志里观察:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch: {}

exporters:
  prometheus:
    endpoint: 0.0.0.0:8889
  debug:
    verbosity: detailed

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]

prometheus.yml 设置 15 秒抓取一次 Collector:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: otel-collector
    static_configs:
      - targets: ["otel-collector:8889"]

grafana/prometheus.yaml 预先配置好数据源,省去界面点击:

apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true

执行 docker compose up -d,Grafana 运行在 3000 端口(用户 admin,密码见 Compose 文件)。

OTLP(OpenTelemetry Protocol)是 OpenTelemetry 定义的数据传输协议,支持 gRPC 和 HTTP/protobuf 两种承载方式,默认端口分别为 4317 和 4318。gRPC 版本使用 HTTP/2 多路复用和二进制编码,吞吐更高、开销更小,适合高频指标上报;HTTP 版本则更容易穿过防火墙和代理。本文在 Collector 中同时开启两个 receiver,而 Claude Code 侧通过 OTEL_EXPORTER_OTLP_PROTOCOL=grpc 选择前者。协议选择只影响传输层,不影响数据语义,因此切换承载方式不会改变指标名称或标签。

让 Claude Code 把数据发过来

在启动 Claude Code 的 shell 中设置以下环境变量:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative
export OTEL_METRIC_EXPORT_INTERVAL=10000
export OTEL_RESOURCE_ATTRIBUTES="team.id=platform"
claude

其中三行值得解释。时间性(temporality):Claude Code 默认是 delta,但 Prometheus 计数器是累计型,Collector 的 Prometheus exporter 文档也没有说明它如何处理 delta 和,所以直接在源头要求 cumulative 可以避免这个问题。导出间隔:默认 60 秒,调试时缩短到 10 秒能让数字更快出现,正常工作后调回默认。资源属性:OTEL_RESOURCE_ATTRIBUTES 的键默认会作为标签加到每个指标数据点上,所以 team.id=platform 免费带来一个团队维度。值不能包含空格。

运行一两个提示词后,检查 Collector 暴露的内容:

curl -s localhost:8889/metrics | grep '^claude_code'

以输出中的指标名为准,不要靠猜。导出器会把点换成下划线,追加单位和 _total,因此成本计数器显示为 claude_code_cost_usage_USD_total,Token 显示为 claude_code_token_usage_tokens_total。标签也遵循同样规则:user.email 变成 user_email。如果什么都没出现,文档建议用 claude --debug-file <path> 并搜索 [3P telemetry] 错误。

要在团队范围推广,把同样的变量放进 Claude Code 托管设置文件的 env 块。文档特别指出,仓库里的 .claude/settings.json 无法开启遥测或重定向数据,所以一个误提交的仓库配置不会把你的数据偷走。

时间性(temporality)是 OTel 指标模型中的一个易被忽视的概念。Delta 时间性表示每次上报的是自上次导出以来的增量,Cumulative 则表示自进程启动以来的累计值。Prometheus 的计数器语义天然是累计型,抓取后由服务端自行计算速率;当 Collector 收到 delta 数据再转成 Prometheus 格式时,需要自行做累加,而不同版本的 exporter 在处理方式上存在差异,容易造成计数错乱或重置抖动。因此在源头直接要求 cumulative,让 Collector 只做格式转换不做状态推断,是更稳妥的做法。

三个面板回答三个问题

创建仪表盘。每个面板选 Bar gauge 可视化,查询类型设为 Instant,并使用 Grafana 的 $__range 变量代表仪表盘时间窗口。把时间选择器改成 30 天,所有面板自动跟随。

谁在花钱:按人统计窗口内的估算美元:

sum by (user_email) (
  increase(claude_code_cost_usage_USD_total[$__range])
)

如果开发者用 API key 而非 OAuth 认证,就没有邮箱可分组(后文详述)。把 user_email 换成 team_id,面板就变成按团队统计。

哪个模型:同一个计数器,不同标签:

sum by (model) (
  increase(claude_code_cost_usage_USD_total[$__range])
)

这个面板最容易引发讨论。当某个模型占主导时,下一个问题就是所做的工作是否真的需要它。

什么类型的活:query_source 把主对话与子智能体、辅助请求分开:

sum by (query_source) (
  increase(claude_code_cost_usage_USD_total[$__range])
)

想更细粒度,可以按 agent_name 或 skill_name 分组。但文档会默认把用户自定义智能体名脱敏为 custom,第三方插件名脱敏为 third-party,除非设置 OTEL_LOG_TOOL_DETAILS=1。注意成本与 Token 计数器上的这个开关需要 Claude Code v2.1.273 或更高版本,更早版本即使设置也会保持脱敏。

还有一个值得用 Stat 面板展示的查询:输入侧 Token 中来自提示缓存的占比。文档指出 input 类型不包含缓存读取和写入,所以分母必须把三者相加:

sum(rate(
  claude_code_token_usage_tokens_total{type="cacheRead"}[1h]
))
/
sum(rate(
  claude_code_token_usage_tokens_total{
    type=~"input|cacheRead|cacheCreation"
  }[1h]
))

如果这个比例突然暴跌,说明缓存策略发生了变化,Token 面板能告诉你从何时开始。

PromQL 中 increase() 与 rate() 都用于处理计数器,但用途不同。increase() 返回区间内估算的增量绝对值,适合直接展示“这段时间花了多少钱”;rate() 返回每秒平均增长速率,适合做占比或趋势对比。两者都基于区间内实际抓取到的样本做外推,因此抓取间隔与区间长度不匹配时会引入误差,这也是短会话可能被少计的根本原因。另外,按标签分组(sum by)会为每个标签值生成独立时间序列,标签基数越高,Prometheus 的内存与查询成本越大,这也是 session.id 需要谨慎开启的原因。

读数字前必须知道的几个坑

这是估算值。文档明确说成本指标是近似值,官方账单数据来自你的 API 提供商(Claude Console、Amazon Bedrock 或 Google Cloud)。如果使用的是按席位订阅的计划,USD 数字应视为相对消耗,而非发票金额。

网关场景先升级。在 Claude Code v2.1.214 之前,通过 ANTHROPIC_BASE_URL 后面的代理、以多帧方式报告用量的流,会让成本和 Token 计数器大约每多一帧就多计一次请求,造成虚高。

API key 和云厂商没有身份。直接使用 API key、Bedrock、Google Cloud 或 Microsoft Foundry 时,只有 user.id(随机安装标识)和 session.id 会被填充。需要自己通过 OTEL_RESOURCE_ATTRIBUTES 附加身份,可以在托管设置或启动包装脚本中按用户设置。

会话 ID 是基数成本。session.id 默认出现在每条序列上,方便下钻但会膨胀长期存储。设置 OTEL_METRICS_INCLUDE_SESSION_ID=false 可以去掉它。

短会话可能少计。每个会话都会开启新序列,而 increase() 基于 Prometheus 实际抓到的样本工作,序列首次抓取前的增量可能丢失。缩短导出间隔能缩小这个缺口,Prometheus 的 created-timestamp-zero-ingestion 特性标志也可能有帮助。

桌面端会话的服务名不同。从 Claude Desktop 应用的 Code 标签页启动的本地会话,service.name 报告为 claude-code-desktop 而非 claude-code。如果你按服务名过滤,需要把两个都加上。

这些数据回答不了什么

面板统计的是花费、Token 和被接受的编辑,但它们不会告诉你工作质量如何。一个被接受的编辑就是一个 decision="accept" 数据点,无论这次改动在代码审查中存活还是第二天被回滚。新增行数也不说明有多少行本应删除。在隐私默认开启的情况下,你也看不到用户问了什么。这是正确的默认值,也正是为什么“这个会话为什么贵”在指标里没有答案。

Claude Code 有处于 beta 的追踪功能,通过 CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 开启,包含 claude_code.interaction span,内部还有 LLM 请求和工具 span。这提供了结构和延迟信息,但仍然不对输出质量给出判断。

这一切度量的是开发者使用的工具。你产品里的 LLM 调用是另一个系统。如果你向客户提供 LLM 功能,那些调用需要自己的追踪(通常遵循 OpenTelemetry GenAI 语义约定)和自己的评估。如果把两者放在同一个仪表盘上,注意一个不匹配:文档指出 Claude Code 不设置 gen_ai.usage.* 属性,且它的 input_tokens 不含缓存 Token,而语义约定会计入。要对比就得把 input、cache read 和 cache creation 相加。

落地建议

Claude Code 的 OpenTelemetry 支持意味着成本可观测性不再是事后审计,而是可以实时拆解到人、模型和调用来源。对于使用多个 AI 编程工具的团队,这套方法也可以复用到其他内置 OTel 的工具上。关键动作只有三步:打开遥测开关、用资源属性注入团队/用户维度、围绕 claude_code_cost_usage_USD_total 和 claude_code_token_usage_tokens_total 建立面板。剩下的工作是根据数据提出正确的问题——比如为什么某个子智能体消耗了不成比例的预算——而不是等待一张总额发票。

#Claude Code#OpenTelemetry#Grafana#Prometheus#AI 成本监控#LLM 可观测性#OTel Collector
分享:

相关推荐