教程Claude Code

用 OpenTelemetry 看自己的 Claude Code 用量:先不装任何东西,把数据接下来看一眼

/usage 只看得到当前会话。Claude Code 能把花费、token、每次模型请求和工具调用通过 OpenTelemetry 导出来。本站用一个五十行的脚本当接收端做了实测,记下三件容易弄错的事:指标是增量不是累计,默认不带提示词和命令内容,写在项目设置里不生效。

发布 · 约 9 分钟读完 · 经人工审核()· 本站的内容怎么来的

本站已实测,测试日期:2026-10-12。

本页目录(10 节)
  1. 它导出两类东西
  2. 最快的看法:打到控制台
  3. 正经一点:发到一个接收端
  4. 收到了什么
  5. 三件容易弄错的事
  6. 这些数据能回答什么
  7. 通过中转站用,有什么不一样
  8. 用 Agent SDK 写的程序也一样
  9. 这一趟花了多少
  10. 这次实测没有覆盖到的

这篇教程解决一个问题:/usage 只告诉你当前这个会话花了多少。想知道这一周哪个模型花得最多、哪个工具最慢、某一句话到底花了多少钱,数据从哪来?

Claude Code 可以把这些数据用 OpenTelemetry(下面简称 OTel)导出去。官方文档主要是写给要给整个公司搭监控的管理员看的,一上来就是采集器和后端。这篇换一个角度:先不搭任何东西,把数据接下来看一眼,弄清楚里面有什么,再决定值不值得接进一套正式的监控。

实测环境
日期 2026-10-12
版本 Claude Code 2.1.296
系统 Ubuntu 24.04,Python 3.12.3
模型 Sonnet 5.5
连接方式 通过酷站AI 中转站,不是直连官方

它导出两类东西

  • 指标:随时间累加的数字,适合画趋势。一共八个:会话数、改动的代码行数、PR 数、提交数、花费、token 数、编辑类工具的批准情况、活跃时长。
  • 事件:一条一条的记录,适合查细节。每次提交提示词、每次模型请求、每次工具调用各一条,另外还有二十多种。

还有第三类,链路追踪,目前是测试版,本文不涉及。

开关是一个环境变量,再加上“往哪发”:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp        # 指标:otlp、prometheus、console、none
export OTEL_LOGS_EXPORTER=otlp           # 事件:otlp、console、none

最快的看法:打到控制台

把两个导出方式都设成 console,数据直接打印出来,什么都不用搭:

CLAUDE_CODE_ENABLE_TELEMETRY=1 OTEL_METRICS_EXPORTER=console OTEL_LOGS_EXPORTER=console claude

本站用非交互模式试了一下,问的是“只回答一个字:好”。结果标准输出有将近 12 KB:

好
{
  resource: {
    attributes: {
      "service.name": "claude-code",
      "service.version": "2.1.296",
      …
  body: "claude_code.managed_settings_resolved",
  …

数据是打到标准输出的,和回答混在一起。 用来看一眼够了。但只要你的脚本要读 claude -p 的输出,就不能开着它。

正经一点:发到一个接收端

真要拿数据做点什么,得有个东西接。正式的做法是跑一个 OTel 采集器,后面接 Prometheus、Grafana 之类。本站没有装这些,写了一个只用 Python 标准库的小脚本当接收端:监听本机,收到什么就一行一条存进文件。

"""一个最小的 OTLP/HTTP 接收端:只听本机,把收到的 JSON 一行一条存下来。"""
import json, sys, time
from http.server import BaseHTTPRequestHandler, HTTPServer

out = sys.argv[1]


class H(BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("Content-Length", "0")))
        with open(out, "a") as f:
            f.write(json.dumps({"t": time.time(), "path": self.path, "body": json.loads(body)}) + "\n")
        self.send_response(200); self.send_header("Content-Type", "application/json")
        self.end_headers(); self.wfile.write(b"{}")

    def log_message(self, *a): pass


HTTPServer(("127.0.0.1", 4318), H).serve_forever()

启动它,然后带着下面这组变量运行 Claude Code:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
export OTEL_METRIC_EXPORT_INTERVAL=5000     # 指标 5 秒发一次,调试用;默认是 60 秒

两点说明:

  • 协议必须设。 文档写明 Claude Code 没有默认的协议,用 otlp 导出就得设 OTEL_EXPORTER_OTLP_PROTOCOL。选 http/json 是因为它是纯文本,标准库就能读。
  • 指标默认 60 秒才发一次。 一个很短的会话可能一次都没发出来就结束了。调试时把间隔调小。

任务是一个带两次工具调用的小问题:“先读 orders.js,再运行 node –test,然后告诉我哪个测试失败”。跑完,接收端收到了 5 个请求:3 个发到 /v1/logs,2 个发到 /v1/metrics。

收到了什么

指标

把收到的数据点按名字和属性汇总:

指标 属性 值
claude_code.session.count start_type: fresh 1
claude_code.cost.usage model: claude-sonnet-5-5,query_source: main,effort: medium 两个数据点:0.067214、0.0083949 美元
claude_code.token.usage 同上,再加 type input、output、cacheRead、cacheCreation 各一组
claude_code.active_time.total type: cli 9.737 秒

花费和 token 都带着模型、effort、来源这几个属性,所以后面可以按模型拆、按主会话和子代理拆。

事件

一共 12 条,按先后:

managed_settings_resolved   启动时:托管设置的解析结果
plugin_loaded × 3           启动时:加载了哪些插件
user_prompt                 提交了一句提示词
tool_decision               Read:放行,依据是配置
tool_result                 Read:成功,13 毫秒
tool_decision               Bash:放行
api_request                 第一次模型请求
tool_result                 Bash:成功,546 毫秒
api_request                 第二次模型请求
assistant_response          给出了回答

最有用的是 api_request,一次模型请求一条,相当于账单的明细:

{
  "event.name": "api_request",
  "model": "claude-sonnet-5-5",
  "input_tokens": "2",
  "output_tokens": "175",
  "cache_read_tokens": "0",
  "cache_creation_tokens": "26184",
  "cost_usd": "0.067214",
  "duration_ms": "4416",
  "ttft_ms": "3494",
  "effort": "medium"
}

每次请求花了多少、读了多少缓存、写了多少缓存、总共多久、第一个字多久到(ttft_ms),都在这里。上面这一条能看出:这次会话的头一个请求,钱几乎全花在写那两万六千个 token 的缓存上。

tool_result 记着每次工具调用的名字、成不成功、用时、输入和输出的大小。tool_decision 记着每次调用是被放行还是被拒绝,以及依据是什么。

每条事件都带一个 prompt.id。同一句提示词引出的所有模型请求和工具调用,这个值是一样的。按它分组,就能算出“这一句话一共花了多少”。

三件容易弄错的事

一、指标是增量,不是累计

cost.usage 收到了两个数据点:0.067214 和 0.0083949。第二个比第一个小,显然不是“到目前为止的总数”。

看原始数据里的 aggregationTemporality,值是 1,也就是增量:每个数据点是“上次导出以来新增了多少”。把两个加起来:

0.067214 + 0.0083949 = 0.0756089

和 Claude Code 自己报的这次运行的花费(0.0756089 美元)一分不差。

自己写脚本处理这些数据的话,要把数据点加起来,不能只取最后一个。本站的汇总脚本第一版就取了最后一个,算出来只有 0.008 美元,差了九倍。接正式后端时,也要确认它按增量来理解这些指标。

二、默认不带内容

默认导出的事件里,提示词和回答是这样的:

{ "event.name": "user_prompt", "prompt_length": "59", "prompt": "<REDACTED>" }

只有长度,没有内容。工具事件里也只有工具名,没有具体的命令和路径。

要内容,得一项一项地打开:

变量 打开之后多了什么
OTEL_LOG_USER_PROMPTS=1 提示词的原文
OTEL_LOG_ASSISTANT_RESPONSES=1 回答的原文。没设的话跟着上一个变量走
OTEL_LOG_TOOL_DETAILS=1 工具的参数:Bash 的完整命令、读写的文件路径、MCP 和 skill 的名字
OTEL_LOG_TOOL_CONTENT=1 工具返回的内容。要开链路追踪才有
OTEL_LOG_RAW_API_BODIES=1 每次模型请求和响应的完整 JSON,里面是整段对话历史

本站打开了第一个和第三个,实测多出来的内容:

user_prompt.prompt         = 先读 orders.js,再运行 node --test,然后用两句话告诉我……
assistant_response.response = 有两个测试失败:“九折券只打一次折”(期望 2250,实际 2025)……
tool_result Read  tool_input      = {"file_path":"/home/ccdemo/demo/h9-otel/app/orders.js"}
tool_result Bash  tool_parameters = {"bash_command":"node","full_command":"node --test 2>&1 | head -80", …}

只设了 OTEL_LOG_USER_PROMPTS,回答的原文也跟着出来了,和文档说的一致。

打开之前想清楚数据发到哪。 这些内容会原样进到你的监控后端:提示词里贴过的密钥、命令行里带的令牌、读过的文件路径。发到自己机器上的文件是一回事,发到一个团队共用的后端是另一回事。最后一个变量尤其要小心,它导出的是完整的对话。

三、写在项目设置里不生效

设置文件有 env 这一项,按说把上面那组变量写进去就不用每次 export 了。本站试了三种放法:

变量放在哪 接收端收到的请求数
shell 里 export 5
项目的 .claude/settings.json 的 env 里 0
启动时用 --settings 传进去 3

写在项目设置里,一个请求都没发出来,也没有任何提示。

文档里有这一条:CLAUDE_CODE_ENABLE_TELEMETRY 和 OTel 导出相关的变量,写在项目设置和项目本地设置里会被忽略,要放在 shell、用户设置或者托管设置里。道理和设置文件那篇里项目文件不能写 bypassPermissions 一样:克隆下来的一个仓库,不应该能把你的使用数据发到它指定的地方去。

想对自己所有的项目都开,写进 ~/.claude/settings.json 的 env。这一种本站没有实测,测试账号的用户设置我们不动。

这些数据能回答什么

想知道 用哪份数据
今天、这周一共花了多少 cost.usage 的数据点相加
哪个模型花得多 同上,按 model 属性分组
主会话和子代理各花多少 同上,按 query_source 分组(main、subagent、auxiliary)
缓存命中得怎么样 token.usage 里 cacheRead 和 cacheCreation、input 的比例
哪个工具最慢、最常失败 tool_result 事件的 duration_ms 和 success
某一句话花了多少 同一个 prompt.id 的 api_request 事件,把 cost_usd 相加
回答慢是慢在等第一个字还是慢在生成 api_request 的 ttft_ms 和 duration_ms
哪些工具调用被拒绝了 tool_decision 事件里 decision 不是 accept 的

这些问题用上面那个存下来的文件加二三十行脚本就能答。要长期看、要画图、要多个人的数据放在一起,才值得去搭采集器和后端。

通过中转站用,有什么不一样

导出通过中转站正常工作。它发的是你指定的地址,和模型接口走哪里无关。有三点要知道:

  • 花费仍然是估算。 cost_usd 和 cost.usage 都是 Claude Code 在本地按官方价格算的,和状态栏、/usage 里的是同一个数。中转站实际扣多少,这里看不到。
  • 缓存的数字是有的。 这家中转站把缓存相关的用量传了回来,所以 cacheRead 和 cacheCreation 有值。
  • 没有账号信息。 数据里只有一个匿名的 user.id,是首次运行时随机生成的,和 Claude 账号无关。没有邮箱,也没有组织。几个人的数据要放在一起区分,得自己用 OTEL_RESOURCE_ATTRIBUTES 加标记,比如 team.id=…。

用 Agent SDK 写的程序也一样

SDK 会继承环境里的这些变量。在容器或者服务的环境里设好,每次 query() 都会导出。数据里有一个 app.entrypoint 属性能区分来源(命令行、Python SDK、TypeScript SDK 等),不过它默认是不带的,要设 OTEL_METRICS_INCLUDE_ENTRYPOINT。本站没有用 SDK 实测这一段。部署时的其他考虑见把智能体放到服务器上。

这一趟花了多少

约 0.35 美元。五次很短的运行,每次 0.06 到 0.08 美元。接收端和汇总脚本在本机跑,不花钱。这是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。

这次实测没有覆盖到的

  • 没有接正式的后端。 采集器、Prometheus、Grafana 都没有装,prometheus 这种导出方式也没有试。
  • 只试了 http/json。 grpc 和 http/protobuf 没有试。
  • 链路追踪没有试。
  • 只在非交互模式下测过,会话都很短。长时间的交互会话里数据量有多大、有没有延迟,没有数据。
  • 事件只见到了七种。 文档列了二十多种,比如接口报错、压缩、子代理完成、hook 执行,这次都没有触发。
  • 写进用户设置的 env 没有实测。
  • 给一个团队用的那些配置没有涉及:按团队打标记、动态请求头、托管设置锁定导出地址。

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章