← 返回蜂巢洞察

使用OpenTelemetry实现Claude Code的可观测性

像 Claude Code 、 OpenAI Codex 、 Google Antigravity 以及 Cursor 这样的代理编码工具,在日常软件开发中已经变得无处不在。 随着代理系统的不断发展,开发者让这些系统完成的大部分工作都是通过逐个分配子任务来实现的。许多团队也在探索并使用共享的、多租户式的代理基础设施,这种架构的成本不会与某个特定的所有者挂钩。在这种情况下,可观测性就成为了监控基础设施成本的关键因素。 在本指南中,您将了解可观测性的工作原理,然后学习如何启用Claude Code内置的遥测功能,运行后端程序来收集数据,并读取该系统生成的各类指标、日志及追踪信息。这些内容将帮助您更

Claude CodeOpenAI CodexGoogle Antigravity以及Cursor这样的代理编码工具,在日常软件开发中已经变得无处不在。

随着代理系统的不断发展,开发者让这些系统完成的大部分工作都是通过逐个分配子任务来实现的。许多团队也在探索并使用共享的、多租户式的代理基础设施,这种架构的成本不会与某个特定的所有者挂钩。在这种情况下,可观测性就成为了监控基础设施成本的关键因素。

在本指南中,您将了解可观测性的工作原理,然后学习如何启用Claude Code内置的遥测功能,运行后端程序来收集数据,并读取该系统生成的各类指标、日志及追踪信息。这些内容将帮助您更有效地跟踪团队的开发成本,而且随着遥测技术的不断成熟,这些数据与实际开发流程之间的关联也会变得更加紧密。

注意:目前Claude Code生成的遥测数据还不包含能够将它们与具体会话建立可靠关联的属性。虽然可以通过session_id来追踪使用情况,但在处理那些包含多种指令或技能的较长会话时,这种方式仍然显得不够便捷。

本指南主要针对Claude Code的遥测功能进行讲解,内容包括指标数据、日志记录以及追踪机制。请注意,本指南仅适用于Linux和macOS系统。

目录

使用OpenTelemetry实现可观测性

可观测性是指能够通过系统生成的数据来了解其运行时的行为。无需深入研究系统的内部结构、安装调试工具、阅读源代码,也无需手动重现相关行为,就可以实现这一目标。

在这里,“系统的运行时行为”指的是那些外部可以观察到的现象。例如,您可以提出以下问题:

  • 95%的请求需要多少时间才能完成?

  • 所有接收到的请求中,失败率是多少?

  • 该服务使用的内存缓存系统的命中率是多少?

  • 某个服务的配置副本数量与实际部署的副本数量之间有什么差异?

对于Claude Code而言,那些难以理解的内部运作机制包括:它是如何管理上下文的、如何将任务分配到多个大语言模型调用中、以及是如何协调各个子代理的。但是,你可以通过分析Claude Code发出的遥测数据来回答以下这些问题:

  • 开发人员或一个团队在一天、一周或一个月内消耗了多少资源。

  • 这些资源是如何分配到不同的模型以及不同工作强度级别的中的。

  • 每花费一美元会消耗多少个令牌,而且这种消耗量在不同类型的操作中会有怎样的差异(例如输入操作、输出操作、缓存读取操作、缓存创建操作等)。

  • 压缩操作是在什么时候触发的,以及它减少了多少上下文相关的令牌消耗量。

只有那些配备了相应监控机制的系统才能回答这些问题。监控代码是由开发人员添加到程序中,或者被内置到相关工具中的;这类代码会记录程序在运行过程中的行为,并将这些信息以遥测数据的形式发送出来。例如,“这个请求消耗了100个令牌”这样的数据就是一种监控结果。

遥测数据能够通过提供系统行为随时间变化的结构化记录,帮助我们及时发现那些潜在的故障。例如,在GitHub在2026年8月17日的系统故障分析报告中,就有一张图表展示了GitHub Actions的执行次数是如何从大约3000万次增加到大约1.1亿次的。

Github Actions Growth

遥测数据

Claude Code发出的遥测数据主要包括三类:

  • 指标数据:这些是在一定时间范围内汇总得到的数值结果,例如每秒接收的请求数量。

  • 日志记录:对单个事件的详细记录,其中会包含时间戳信息。例如,Claude Code中的压缩操作事件记录。

  • 跟踪数据:记录某个请求在系统中的执行路径,这些数据会按照时间顺序被分解成多个子请求。例如,在一个电商网站上,记录用户下达订单请求后,系统会调用哪些内部服务来完成这个请求。

OpenTelemetry(https://opentelemetry.io/)是一个用于实现系统可观测性的框架,它可以帮助你生成、收集这些遥测数据,并将其传输到后端进行处理,后端可以负责数据的存储、查询和可视化展示。由于这种分离机制的存在,无论使用哪种工具或供应商提供的后端服务,这个框架都能正常工作;而且OpenTelemetry为多种编程语言提供了相应的监控开发工具包。

为Claude Code添加监控代码

通常,监控代码会与应用程序一起运行,在那些最适合进行数据测量的位置被执行:例如在请求到达时或响应发出时,或者在计算令牌数量的时候。

它可以通过两种方式集成到应用程序中:

  1. 使用共享的仪器库:那些基于开源框架开发的应用程序可以将此类仪器库作为依赖项添加到项目中,并将其与应用程序的生命周期方法关联起来。OpenTelemetry为许多开发框架提供了相应的仪器库(例如:Spring Framework)。

  2. 由应用程序开发者自行进行定制实现:可以使用OpenTelemetry SDK直接在代码中添加用于生成遥测数据的模块。对于那些采用闭源架构的产品来说,虽然其代码是私有的,但它们仍然能够生成符合OpenTelemetry标准的遥测数据。

示例:HTTP遥测技术的应用

在处理HTTP请求的过程中,中间件是所有请求都会经过的路径,因此它也被用来配置诸如认证这类与整个应用程序相关的功能。正因为如此,中间件也是放置遥测代码的理想位置:只需在中间件中添加相应的逻辑,就能对每一条请求进行测量。

HTTP遥测示例

该图示展示了遥测代码在请求处理流程中的位置。

  • 客户端发送的请求在到达应用程序的处理逻辑之前,会先经过HTTP中间件。中间件会利用SDK提供的功能来记录处理逻辑的执行时间、状态以及相关统计信息。

  • 随后,SDK会将这些测量结果暂存起来,并通过后台线程将数据发送到OTLP收集器中,整个过程与请求处理流程是分离的。

Claude Code就是另一个例子。在这个案例中,核心应用程序及其遥测模块都是由Anthropic提供的。用于统计令牌使用情况、成本以及工具调用次数的代码是内嵌在应用程序中的,并且会通过OTLP协议来发送遥测数据。作为Claude Code的用户,您只需要启用遥测功能,并准备一个后端系统来接收和处理这些数据即可。

注意: OTLP是一种专为OpenTelemetry项目设计的遥测数据传输协议。本指南假设所有系统都能与OTLP兼容;但如果使用不兼容的组件,可能会导致不可预测的结果,因此这类情况不在本指南的讨论范围内。

要收集、存储和查看遥测数据,您需要以下组件:

拉取模式与推送模式:遥测数据是如何离开应用程序的

遥测数据会通过以下两种方式之一离开应用程序:

拉取模式:应用程序会在一个HTTP端点上公开其当前的指标数据,而采集工具(如Prometheus)会定期读取该端点上的数据。每个运行中的实例都需要拥有自己的端口,而且采集工具必须事先知道所有这些端点的地址。在这种模式下,应用程序处于被动状态,数据传输是由采集工具来驱动的。这种模式适用于那些地址稳定、生命周期较长的进程。

在OpenTelemetry中,拉取模式的配置方式为OTEL_METRICS_EXPORTER=prometheus(有关可使用的导出器值,请参阅SDK环境变量)。

按照惯例,应用程序会将其指标数据发布在http://localhost:9464/metrics这个地址上。这种导出器仅用于处理指标数据。

推送模式:应用程序会定期将遥测数据发送到接收端点。这种模式更加灵活:任意数量的进程都可以向同一个端点发送数据,而且无需事先进行注册;因此,即使应用程序的地址发生变化,它们也可以自由地启动或停止。

在OpenTelemetry中,推送模式的配置方式为OTEL_METRICS_EXPORTER=otlp,该导出器通过OTLP协议来传输数据。

推送模式不仅可以传输指标数据,还可以传输日志和跟踪信息。

何时启用采集器

当现有的技术架构无法满足系统规模扩大和复杂性增加的需求时,就需要部署采集器。采集器可以在以下方面发挥重要作用:

  • 简化配置:所有数据生产者都只需指向同一个采集器,而无需各自配置后端导出器。

  • 仅支持出站连接:企业网络通常会阻止基于拉取模式的采集工具所需的入站连接。通过使用采集器,应用程序可以将数据推送给采集器,再由采集器将数据转发给相应的后端,因此无需任何后端接受入站请求。

  • 数据转换与分发:采集器可以将遥测数据转换为特定供应商支持的存储格式,并将相同的数据发送到多个后端。

  • 缓冲处理:如果某个后端出现故障,采集器可以暂时保存数据并重新尝试发送,从而避免因短暂故障导致数据丢失。

  • 数据处理:在数据被存储之前,采集器可以对数据进行加工处理,例如删除某些敏感信息。

注意:尽管本指南中的示例是针对单用户环境进行的配置,但实际上在部署采集器时,系统通常会包含多个后端。由于这些后端的存储和查询方式可能不同,因此让采集器接收来自所有数据生产者的数据,然后再将它们分别转发到相应的后端,要比直接为每个应用程序连接三个后端更为简便。正因为如此,对于个人使用来说,收集器可能是可选的;但在生产环境中,它绝对是必不可少的——随着数据生产者数量和后端数量的增加,收集器的价值会变得更加显著。

先决条件

本指南的每一部分都会提供相关文档的链接,但如果您已经熟悉以下工具和查询语言,那么学习进程将会更加顺利:

您需要准备以下内容:

  • Claude Code的最新版本

  • Docker Engine(29.4.1及以上版本),以及Docker Compose

    • 至少需要5个容器的运行能力(4核CPU、8GB内存、15GB硬盘空间)
  • 一个包含增强型遥测功能测试版的Claude Plan(Pro+或Max套餐)

  • Git

  • 请确保本地主机上的以下端口是空闲的:

    • 3000(用于Grafana)

    • 3100(用于Loki)

    • 4317/4318(用于OTel Collector的OTLP协议,支持gRPC和HTTP接口)

    • 9090(用于Prometheus)

    • 16686(用于Jaeger UI)

还有一些知识也会对您有所帮助:

设置

测试用可观测性组件是通过Docker Compose来部署的。为了使遥测数据能够被正确导出,Claude Code必须能够访问位于同一台机器上的Collector的OTLP端点:当在同一台机器上运行时,该端点的地址为localhost:4317(使用gRPC协议)或localhost:4318(使用HTTP协议)。所有的后端服务都运行在容器中,并且各自拥有独立的Docker卷用于数据持久化存储。

仪器配置

该图示展示了我们所使用的各类遥测组件是如何相互连接的。

  • 除了Claude Code之外,所有其他组件都是通过Docker Compose管理的容器来运行的。

  • 只要任何一台机器(无论是本地主机还是云虚拟机)上运行的Claude Code实例能够与Collector建立连接,那么这些实例就都能够将遥测数据导出给Collector。具体来说,运行 Collector容器的那台机器的4317/4318端口必须是可访问的。

从上到下的逻辑流程如下:

  • Claude Code会通过OTLP协议将三种类型的遥测数据都导出给Collector。

  • 随后,Collector会将这些数据按类型进行分类:将追踪数据发送到Jaeger,将日志数据发送到Loki,同时会在8889端口上暴露指标数据,以便Prometheus能够对这些数据进行抓取。

  • Jaeger、Prometheus和Loki各自会将处理后的数据存储在各自的Docker卷中。

  • Grafana会将这些数据整合起来,形成一个统一的仪表盘界面供用户查询。

我们的目标是实现这样一种机制:让Claude Code生成的遥测数据能够实时被传输到后端存储系统中,并且可以在需要时随时进行查询——无论是在会议进行期间,还是会议结束后很长时间。为此,必须满足以下两个条件:

  • 首先需要在Claude Code中启用遥测功能。虽然相关的监测工具已经被内置在了代码中,但只有在启用了遥测功能并且将其OTLP导出器配置为指向Collector之后,这些数据才会被真正发送出去。

  • 其次,必须运行后端服务。Collector会负责处理接收到的所有遥测数据,并将其转发给Prometheus、Loki和Jaeger进行进一步存储或查询处理。

你需要先启动后端服务,这样遥测数据才会有地方被存储和处理。

启动可观测性后端服务

在启用Claude Code的遥测功能之前,请确保整个系统已经能够正常收集、处理和读取数据。测试用可观测性后端的代码保存在这个Github仓库中。

该仓库的结构如下:

.
├── README.md
└── compose
    ├── docker-compose.yml # 用于配置可观测性组件堆栈中的5个容器的Docker配置文件。其中指定了各服务的镜像版本、端口映射以及命名卷设置。
    ├── grafana
    │   └── provisioning
    │       ├── alerting
    │       ├── dashboards
    │       ├── datasources # 包含Prometheus、Loki和Jaeger的数据源配置,以及仪表盘相关设置。初始状态下这些目录是空的。
    │       └── plugins
    ├── jaeger-config.yaml # Jaeger v2的配置文件,规定了追踪数据的存储方式(使用本地文件系统)。注意:此配置与root用户账户有关。
    ├── loki-config.yaml # Loki的配置文件,采用了简单的文件系统存储方式。配置参数基本为默认值。
    ├── otel-collector-config.yaml # 定义了数据接收、处理和导出的流程:遥测数据通过4317/4318端口进入系统,追踪数据被发送到Jaeger,日志数据被保存到Loki,而指标数据则会在8889端口上暴露出来供Prometheus抓取。
    └── prometheus.yml # 定义了针对Collector的8889端口的定期数据抓取任务,抓取间隔为30秒。

您只需要使用docker compose命令即可启动这些容器。该命令会读取docker-compose.yml文件,然后启动相应的容器,并将它们与各自的配置文件关联起来。

容器之间的连接性:

所有容器都在同一个Docker网络中运行,因此它们可以直接使用容器名称进行通信。例如,在collector的配置文件中,就使用了容器名称:

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889
  otlphttp/loki:
    endpoint: http://loki:3100/otlp

需要注意的是,该配置文件中并不包含Prometheus的配置信息,因为Prometheus实际上会从collector那里获取这些数据,具体配置方式详见prometheus.yml文件:

global:
  scrape_interval: 30s

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

使用Docker compose来启动这些容器:

git clone https://github.com/ps-mir/otel-dev-stack.git
cd otel-dev-stack/compose
docker compose up -d

# 输出结果
✔ Volume compose_loki_data                          已创建                                                                                                                                          0.0秒
✔ Volume compose_grafana_data                       已创建                                                                                                                                          0.0秒
✔ Volume compose/prometheus_data                    已创建                                                                                                                                          0.0秒
✔ Volume compose_jaeger_data                        已创建                                                                                                                                          0.0秒
✔ Network compose_default                           已创建                                                                                                                                          0.1秒
✔ Container compose-prometheus-1                    已启动                                                                                                                                          4.1秒
✔ Container compose-loki-1                          已启动                                                                                                                                          4.2秒
✔ Container compose-jaeger-1                        已启动                                                                                                                                          4.3秒
✔ Container compose-otel-collector-1                已启动                                                                                                                                          3.3秒
✔ Container compose-grafana-1                       已启动                                                                                                                                          2.7秒

检查容器状态:

# 所有五个服务都应该显示为“Up”状态
docker compose ps

# 输出结果
NAME                       IMAGE                                              COMMAND                  SERVICE          CREATED         STATUS         PORTS
compose-grafana-1          grafana/grafana:13.2.0                            "/run.sh"                grafana          3分钟前   Up 3分钟   0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp
compose-jaeger-1           cr.jaegertracing.io/jaegertracing/jaeger:2.20.0   "/go/bin/jaeger --co…"   jaeger           3分钟前   Up 3分钟   0.0.0.0:16686->16686/tcp, [::]:16686->16686/tcp
compose-loki-1             grafana/loki:3.7.6                                "/usr/bin/loki -conf…"   loki             3分钟前   Up 3分钟   0.0.0.0:3100->3100/tcp, [::]:3100->3100/tcp
compose-otel-collector-1   otel/opentelemetry-collector-contrib:0.159.0      "/otelcol-contrib --…"   otel-collector   3分钟前   Up 3分钟   0.0.0.0:4317-4318->4317-4318/tcp, [::]:4317-4318->4317-4318/tcp, 55679/tcp
compose-prometheus-1       prom/prometheus:v3.11.2                           "/bin/prometheus --c…"   prometheus       3分钟前   Up 3分钟   0.0.0.0:9090->9090/tcp, [::]:9090->9090/tcp

提示: Jaeger在运行时会以root用户的身份创建badger目录。如果不这样做,就会出现“权限不足”的错误:mkdir /badger/key: permission denied。虽然Jaeger本身不需要root权限,但Docker卷在首次挂载时会被设置为root:root所有。

启用遥测功能

一旦将OpenTelemetry监控工具添加到应用程序中,该功能会处于禁用状态,直到通过特定配置将其启用为止。

在Claude Code中,有两种方法可以启用遥测功能:

1. 使用环境变量

设置相应的环境变量即可启用遥测功能的生成。除了标准的OTEL_*变量外,Claude Code还定义了自己的CLAUDE_CODE_*变量。

本指南使用以下配置:

# 默认设置:如果未设置或值为0,Claude Code不会生成任何遥测数据
export CLAUDE_CODE_ENABLE_TELEMETRY=1

# 启用beta版本的增强型遥测功能及相关属性和事件记录
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1

# 选择具体的信号导出方式;"otlp"表示通过OTLP协议传输数据
# 其他可选值包括"console"(在本地显示数据)、"prometheus"(仅输出指标数据)以及"none"(不传输任何数据)
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_TRACES_EXPORTER=otlp

# OTLP传输协议:"grpc"表示通过4317端口传输数据;"http/protobuf"则使用4318端口
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc

# 所有三种信号的统一接收端点:本地机器上收集器的OTLP监听器地址
export OTEL_EXPORTER.otLP_ENDPOINT=http://localhost:4317

# 指标数据更新的频率,单位为毫秒;默认值为60000毫秒(即60秒)
# 为了便于手动检查,这里将更新间隔设置得较短
export OTEL_METRIC_EXPORT_INTERVAL=5000

# 输出累积计数值而非差分值(详见下文“数据聚合方式”部分)
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative

然而,环境变量是整个进程范围内有效的,因此它们可能会影响不仅仅是Claude Code。例如:

  • 可能会在其他应用程序中无意中启用数据采集功能。

  • 如果你在自行开发数据采集模块或使用任何OpenTelemetry SDK,就可能会干扰OpenTelemetry的相关代码或测试流程。

2. Claude Code的`settings.json`文件

OpenTelemetry定义了基于YAML格式的声明式配置机制,用于启用数据采集功能,但Claude Code并不支持这种配置方式。不过,你仍然可以在`~/.claude/settings.json`文件中设置相应的环境变量。虽然这不属于声明式配置,但它比shell环境变量更有效,因为这些变量仅适用于Claude Code。举个例子:

{
  "effortLevel": "medium",
  "tui": "fullscreen",
  "env": {
     "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
     "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
     "OTEL_METRICS_EXPORTER": "otlp",
     "OTEL_LOGS_EXPORTER": "otlp",
     "OTEL_TRACES_EXPORTER": "otlp",
     "OTELEXPORTER_OTLP_PROTOCOL": "grpc",
     "OTEL_EXPORTER_otLP_ENDPOINT": "http://localhost:4317",
     "OTEL_METRIC-export_INTERVAL": "5000",
     "OTEL_EXPORTER.otLP_metRICS_TEMPORALITY_PREFERENCE": "cumulative"
  }
}

对于数据采集功能而言,只有`env`块中的设置才具有实际意义。`effortLevel`和`tui`则是与数据采集无关的配置选项,你可能已经设置了这些选项。上述环境变量名称与OpenTelemetry规定的名称是一致的。

聚合时间范围

Prometheus中属于计数器类型的指标其数值只会随时间不断增加。因此,直接查看这些指标的原始值是没有意义的,通常需要通过计算每秒的增长幅度(`rate()`)或某个时间窗口内的总增长量(`increase()`)来分析它们。

“聚合时间范围”决定了在每次数据导出时,计数器会报告哪些数值:是自上次导出以来的变化量(Delta),还是从进程启动以来的累计值(Cumulative)。

举个简单的例子。假设Claude Code在四个5秒钟的时间间隔内消耗代币:

)
导出时间 自上次导出以来消耗的代币数 本次导出的变化量累计消耗的代币数
0秒(开始时) -- -- 0
5秒 100 100 100
10秒 0 0 100
15秒 250 250 350
20秒 50 50 400

默认情况下,Claude Code会以`AggregationTemporality: Delta`的方式生成指标数据。你可以通过以下命令在收集器的容器日志中查看这一设置:

# 该命令仅适用于包含docker-compose.yml文件的目录
docker compose logs otel-collector

注意:若要在Collector中启用详细日志记录,您需要将debug导出器添加到Collector配置文件中。

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger, debug]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus, debug]

然后重新启动容器:

# 该命令仅能在包含docker-compose.yml文件的目录中执行
docker compose up -d --force-recreate otel-collector

当使用AggregationTemporality: Delta进行日志输出时,日志格式如下:

otel-collector-1  | 描述符:
otel-collector-1  |      -> 名称:claude_code.active_time.total
otel-collector-1  |      -> 描述:总活跃时间(以秒为单位)
otel-collector-1  |      -> 单位:秒
otel-collector-1  |      ->> 数据类型:求和
otel-collector-1  |      ->> 是否单调递增:true
otel-collector-1  |      ->> 聚合时间范围:Delta <---
otel-collector-1  | 数值数据点数量 #0

对于Prometheus提供的rate()/increase()等函数而言,Delta时间范围并不适用,因为这些函数期望接收累积值。

OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative这个配置项设置为“cumulative”,就可以使导出的指标数据采用累积时间范围进行计算。本指南中既在环境变量设置中提到了这一点,也在JSON配置文件中进行了说明。

与之前一样,任何配置更改后都需要重新启动Collector容器,才能让新的配置生效。

探索遥测数据

一旦遥测数据开始流动,您就可以开始对这些数据进行分析查询了。指标数据、日志记录以及跟踪信息分别能够帮助我们了解Claude Code的使用情况,因此以下这三个部分在功能上是相对独立的。

这些数据来源于两个途径。

  • “指标数据”和“日志记录”这两个部分会显示后端系统中累积的Claude Code使用数据,因此您看到的数据会反映您自己实际进行的操作,而这些数值可能与截图中的内容不一致。在看到有明显的结果之前,请先进行几次真实的操作。

  • 而“跟踪信息”部分则会展示一次特定的操作流程——这种自定义生成的流程能够详细地反映一系列会议的执行过程,您无需重新执行这些操作即可查看相关数据。

指标数据

指标数据是对Claude Code使用情况进行的汇总分析,它是按照特定时间窗口进行统计得出的结果。例如,总成本、token的使用量,以及这些数值随模型工作难度或token的类型等属性的变化趋势等。利用这些指标数据,您可以监控使用情况并及时发现消费模式中的变化。

每一项指标数据都是随着时间推移而记录下来的数值,它们属于时间序列数据,您可以对这些数据进行绘制或汇总分析。Claude Code的指标数据都是累计值,因此查询结果反映的是在指定时间窗口内的变化情况,而非原始数值。具体原理请参见上文聚合时间范围部分。

Prometheus正是我们在这里使用的指标后端工具。它负责从数据收集器中获取数据、存储这些指标信息,并能够响应使用PromQL编写的查询请求。Grafana会从localhost:3000这个地址读取相同的数据,用于生成仪表板界面。所有指标及其属性的完整列表可以在Claude Code监控文档中找到。

在浏览器中打开Prometheus(地址为localhost:9090),在查询框中输入claude,你应该能看到所有被支持的指标:

Prometheus中可用的Claude Code计数型指标。

对于下面列出的每一个指标,你首先会使用PromQL来查询这些数据,然后会用同样的查询语句将这些数据添加到Grafana仪表板中作为面板显示。

总支出金额(美元)

claude_code_cost_usage_USD_total这个指标表示每次会话中累计产生的使用成本,单位为美元。这个指标对于控制预算以及发现使用量突然增加的情况非常有用。

这个数值是基于Anthropic提供的按模型、按类型计价的令牌数量计算得出的。因此,它的数值有时会超过你购买的Claude Code套餐的订阅费用,这是完全正常的。

注意:如果你是按照每次API调用的次数来付费的话,这个指标就显得尤为重要了。而订阅服务会为你提供一定的使用额度,并且会设置更高的但有限的速率限制。

sum(increase(claude_code_cost_usage_USD_total[10m]))

increase(...[10m])这个表达式用于计算过去10分钟内该指标的增长情况。sum(...)如果没有by子句,那么所有基于不同属性的统计数据(如modeleffort等)都会被合并成一个数值。

sum(increase(claude_code_cost_usage_USD_total[$__range]))

如果想要将这些数据添加到Grafana仪表板中作为面板显示,可以先在Prometheus中测试上述查询语句,然后选择“Explore”功能,将数据源设置为Prometheus,并运行该查询。最终显示的结果会取决于你在指定时间范围内实际使用了多少Claude Code服务。

Grafana中的总支出金额统计图表——通过指标浏览器查看查询结果。

在将这个指标添加到仪表板后,你会看到更多的面板设置选项。请选择Stat面板来显示这些数据。

总美元统计面板

需要将此面板添加到Grafana仪表板中。

总代币使用量

claude_code_token_usage_tokens_total表示累计代币数量,其统计格式与“美元成本”指标相同。首先需要在Prometheus中查看原始数据序列,以确定可以按哪些标签进行汇总分析。单个数据序列的示例如下:

claude_code_token_usage_tokens_total{effort="high", exported_job="claude-code", instance="otel-collector:8889", job="otel-collector", model="claude-sonnet-5", otel_scope_name="com.anthropic.claude_code", otel_scope_version="2.1.252", query_source="auxiliary", session_id="e0b9795b-4da3-4171-8fa6-a2866bf44d86", terminal_type="ssh-session", type="cacheCreation"} 213730

末尾的数字即为统计结果。您需要重点关注的属性包括typemodeleffort。下方的按类型划分的代币使用情况图表就是按照type进行分类展示的。

“窗口总计”这一指标的统计格式与总美元支出指标相同,其计算公式为:

sum(increase(claude_code_token_usage_tokens_total[$__range]))

每美元对应的代币数量

与前两个指标不同,这个数值是通过将“总代币数量”除以“总成本”计算得出的。

sum(increase(claude_code_token_usage_tokens_total[$__range])) / sum(increase(claude_code_cost_usage_USD_total[$__range]))

这个数值会将所有不同的属性组合合并为一个最终结果。例如,模型A在中等努力程度下的使用情况与模型B在高努力程度下的使用情况分别属于不同的数据序列,而查询结果会汇总这些数据。

如果要分析特定的属性组合,可以按该属性对数据进行分组后再进行计算比较。

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

只需将model替换为efforttype即可;上述原始数据序列中还列出了其他可使用的标签。

最终分析结果:

汇总统计面板

统计面板(6小时时间窗口)显示:总美元支出为4.28美元,总代币使用量为302万,每美元对应的代币数量为70.7万。

按类型划分的代币使用情况

您可以看到claude_code_token_usage_tokens_total这一指标随时间的变化情况,并且这些数据是按照type进行分类展示的。单独查看一个统计数值会掩盖其变化趋势,因此建议使用时间序列面板来进行分析。

type属性有四种取值,这些取值在成本上存在显著差异:

  • cacheRead:从现有缓存中获取的令牌。在较长的会话过程中,这类令牌的使用量占主导地位,其成本也低于基准费率

  • cacheCreation:在首次加载数据时写入提示缓存的令牌。这类操作的成本较高。

  • input:新的、未缓存的提示令牌。

  • output:模型生成的令牌。

若想查看总计数值,可以同时使用两种查询方式:一种是按类型进行细分统计,另一种则是获取未分类的总计结果以供参考。

# 按类型细分统计
sum by (type) (increase(claude_code_token_usage_tokens_total[$__rate_interval]))
# 总计
sum(increase(claude_code_token_usage_tokens_total[$__rate_interval]))

__rate_interval是Grafana用于时间序列图表的逐步时间间隔设置,与上面用于统计图表的__range参数的作用类似。

Graphana 时间序列图表浏览界面

在将这两个查询结果保存为图表之前,先在Grafana的浏览界面中运行它们。

将其添加到仪表板后:

按类型划分的令牌使用情况

每条曲线上的峰值代表Claude Code系统的短暂高负荷运行状态,而平缓的部分则表示系统处于空闲状态。将鼠标悬停在某一点上,可以查看该时间段内四种类型的令牌使用情况:此处总计约有100万令牌,其中cacheRead类型占约92.5万令牌,其余为cacheCreationoutputinput类型。

提示:令牌消耗量主要由cacheRead类型决定,而且这种类型的成本也是最低的。

按模型与工作强度划分的令牌使用情况

这就是“每美元令牌消耗量”这一统计指标的具体体现:哪些模型工作强度组合实际上会消耗最多的令牌。

sum by (model, effort) (
  increase(claude_code_token_usage_tokens_total[$__rate_interval])
)
按模型与工作强度划分的令牌使用情况

在这里,令牌使用量是按照模型工作强度这两个属性进行分类统计的。在这个示例中,所有数据都对应于claude-sonnet-5模型,在mediumhigh工作强度下运行;其中在18:13左右出现的一次medium强度运行过程产生了约260万令牌的消耗量。不同分类组合的数量取决于所选属性的取值范围大小。

日志

日志记录是一种带有时间戳的事件记录,其中会包含所有相关的字段信息。当你需要了解某次具体事件的发生情况以及其背景信息时,就可以查询这些日志——比如发生了什么、何时发生的、涉及哪些数值等等。

指标则是同一类数据的预先聚合结果。任何需要进行计数、求和或计算百分位数的操作都属于指标的范畴。如果你在后续处理过程中对日志数据进行了汇总分析,那么这些数据从一开始就应该被视作指标来处理。

日志适用于以下场景:

  1. 了解单次事件的具体细节:获取某次事件发生的全部详细信息,而不仅仅是汇总后的数字结果。

  2. 用于处理离散或不规则发生的事件:比如系统的压缩操作、会话的开始、API错误等等。

  3. 在事件发生后进行故障排查:在出现问题后,可以通过查看原始日志记录来进行调试。

  4. 实现日志与相关追踪数据之间的关联分析:一条包含追踪ID和跨度ID的日志记录,可以帮助你直接找到该记录所属的请求信息。

我们在这里使用的日志后端是Loki,查询语言为LogQL。通过这样的后端来处理日志数据,可以带来以下优势:

  1. 结构化的字段:可以通过命名的字段来进行过滤和计算操作,而无需对文本内容使用正则表达式进行解析。

  2. 字段索引功能:只需输入标签名称,就能快速查找相关数据,而无需扫描所有日志记录。

  3. 追踪数据之间的关联分析:可以从一条日志记录追溯到其所属的追踪信息,或者提取与某条追踪信息相关的所有日志记录。

  4. 基于时间范围的查询功能:每个查询操作都针对特定的时间窗口进行,因此能够有效降低扫描成本。

Grafana在生成仪表盘时也会从Loki中获取数据,这与它处理Prometheus数据的方式是一样的。

压缩事件

当Loki中的数据量过大时,系统会自动进行压缩操作。每次压缩都会生成一条日志记录(event_name="compaction"),其中会包含压缩前后的标记数量(pre_tokens, post_tokens)以及该压缩操作所属的span_id。因此,通过查询这些日志记录,就可以了解压缩操作的频率以及每次压缩所回收的数据量。

在Grafana的“探索”功能中,选择Loki作为数据源,然后粘贴下面的LogQL查询语句。该查询语句会选中所有的压缩事件,使用logfmt函数解析这些事件的字段信息,并通过label_format函数计算出每次压缩所带来的数据减少百分比。需要注意的是,这些字段只有在实际发生了压缩操作之后才会被创建出来,因此需要先触发几次压缩操作才能获取到这些数据。

在这里,为每条记录计算reduction_percentage是可行的,因为这个数值是与具体事件相关联的。而如果要对多次压缩操作进行平均计算,那么这些数据就应该被归类为指标。

针对Loki数据源的压缩事件查询语句。label_format这一配置用于添加reduction_pct标签。若想以表格形式显示这些数据,可将面板切换为“表格视图”,并执行以下三项Grafana转换操作:

  1. 从标签对象中提取相关字段。

  2. 根据字段名称进行过滤,保留Time、pre_tokens、post_tokens、reduction_pct以及span_id这些字段。

  3. 将相关字段的类型转换为数值形式,以便pre_tokens、post_tokens和reductionpct能以数字形式显示。

压缩事件信息,包括前置/后置标记数量及计算出的降低百分比。

跟踪

跟踪功能能够详细展示请求在应用程序中从开始到结束所经过的完整路径。以下是跟踪技术背后的一些基本概念:

  • Span:表示一项具体操作的时间戳记录,是构成跟踪数据的基本单元。所有跟踪信息都会以一系列Span的形式被记录下来,每个Span都有其特定的类型及操作名称:

    • claude_code.interaction:指Claude Code为响应某个用户请求而执行的所有操作。通常来说,这个Span就是整个跟踪过程的代表。

    • claude_code.llm_request:表示在某次交互过程中发生的特定模型调用。

    • claude_code.tool:指在某次交互过程中执行的特定工具命令(如BashWriteAgent等)。

  • Trace:由多个Span组成的树状结构,用于表示请求从开始到结束所经过的完整路径。

  • Session:指一次Claude Code的执行过程,其身份由session.id标识。一次执行可能会产生多个交互事件和相应的跟踪记录。

  • Subagent:由Agent工具启动的嵌套式Claude Code实例,它会独立执行自己的交互操作。

Jaeger是此处使用的跟踪后端工具。收集器会通过OTLP将Span数据转发给Jaeger,Jaeger会存储这些数据,并允许用户按服务或Span标签搜索跟踪记录,同时还能以树状结构查看每条记录的详细内容。下面所有操作都是通过地址localhost:16686访问Jaeger的UI来完成的。

生成跟踪记录

为了生成跟踪数据,本指南会使用一个测试提示来启动多个子代理并生成相应的文本内容。这个测试提示是在中等复杂度下使用Sonnet 5工具进行验证的。

你可以直接将这个提示代码粘贴到Claude Code中:

并行启动4个子代理,每个子代理负责处理下面的一个主题。每个子代理都会根据自身掌握的知识来研究该主题,并返回一份约150字的总结,其中包含3个关键点。请注意,这些子代理不得读取文件或执行任何命令。
主题列表:
1. TCP拥塞控制机制的工作原理
2. CAP定理
3> DNS解析过程
4> Bloom过滤器的概念

当所有4个子代理都完成处理后,请将它们的总结内容合并成一份Markdown格式的文档,并保存到summary.md文件中。
运行提示命令时的截图。

注意:在提示命令执行完成后,向Claude Code请求同一会话中的session_id。这个ID将用于在Jaeger中查找相关的追踪记录。

按会话ID查询追踪记录

通过session_id查找追踪记录。

搜索条件为service = claude-code以及标签session.id=<id>。查询结果返回了6条追踪记录,这些记录都源自claude_code.interaction,其跨度数量介于1到20之间,持续时间从大约1秒到33秒不等。

仅通过这个列表是无法判断每条追踪记录具体执行了什么操作的。需要手动逐一查看这些记录,或者使用针对真实会话的追踪API进行进一步分析,才能得到详细信息:

)
# 追踪记录名称 跨度数量 持续时间 LLM调用次数使用的工具
1 claude_code.interaction 1 1.4秒 0
2 claude_code.interaction 3 4.6秒 2
3 claude_code.interaction 1 5.4秒 0
4 claude_code.interaction 1 2.5秒 0
5 claude_code.interaction 20 15.7秒 7 Agent(x4)
6 claude_code.interaction 15 32.5秒 5 ScheduleWakeup(x2), Write(x1)

一些观察结果:

  • 其中有一半的追踪记录其实并无实际意义。记录1、3和4都是仅包含一个跨度的交互操作,既没有模型调用也没有使用任何工具,只是对空闲会话进行的简单检测;记录2则是简短的通信过程;只有记录5和6才真正包含了有意义的操作。

  • 这些并行执行的操作实际上都属于同一个交互流程。在记录5中,四个Agent调用都是在一个claude_code.interaction范围内完成的。它们嵌套的模型调用时间各为7到10秒,但这些操作是同时进行的,因此整个交互过程仅耗时约16秒,而所有子代理的模型调用总共花费了大约35秒。

  • 每个子代理的模型调用都被包含在其对应的Agent跨度中,并且每个调用都会携带一个agent_id,因此可以区分出这四个不同的子代理。

  • agent_id本身并不提供具体的信息。系统中没有agent.nameskill.name这样的字段,因此无法通过这个ID了解每个子代理的具体功能或执行的操作内容。

  • 虽然每个跨度都会记录token的数量,但并不会显示对应的费用金额。每个claude_code.llm_request都会包含input_tokensoutput_tokenscache_read_tokenscache_creation_tokens这些信息,但却没有美元费用的数值。

  • 记录6中的“写入操作”属于另一个独立的交互过程。该记录中没有Agent跨度:首先有一个持续时间约为23秒的claude_code.llm_request》用于生成最终的markdown内容,随后才进行了短暂的Write操作;而那两个ScheduleWakeup跨度则属于后台协调任务。

提示:通过跟踪记录,你可以看到四个子代理的调用信息,每个调用都包含agent_id、令牌计数以及时间信息,但这些数据无法说明某个子代理被分配了处理哪个主题的任务。相比之下,指标数据可以通过modeleffortskill等属性来明确显示任务分配情况。

注意:在交互跟踪记录中,userprompt字段默认会被隐藏。将参数OTEL_LOG_USER_PROMPTS设置为1可以取消这一设置,从而记录原始的提示文本。但在多用户/多租户环境中,请避免启用此功能,因为这会使得任何能够访问遥测后端系统的人都能看到提示内容。

结论

本指南全面介绍了Claude Code中的可观测性功能,包括其遥测机制以及如何收集三种类型的数据并在本地后端进行分析。

指标数据可以帮助你按特定属性划分某段时间内累积产生的成本和使用情况。在共享或多租户环境中,这一点尤为重要,因为这些环境中的成本通常不会与某个特定的用户或团队相关联,因此必须有人负责对这些成本进行核算。

日志记录了各个单独的事件,对于了解某个具体操作过程中发生了什么变化非常有用,例如数据压缩操作的具体过程。

跟踪记录展示了某个提示是如何被分解为多个子代理调用以及模型调用的,同时还会显示这些调用的时间信息和令牌计数。这是进行调试或优化复杂的多代理提示系统的起点,不过目前的跟踪系统还无法确定是哪个具体的提示或技能导致了某次调用。

部分遥测功能目前仍处于测试阶段,因此相关参数的名称和属性可能会发生变化,而且像“按调用分配数据”这样的功能也可能在后续版本中得到完善。因此,建议在相关功能正式稳定后,再次查看监控文档以获取最新信息。

参考资料

相关文章

技术实践

如何从大型语言模型中获取可靠的结构化数据

大多数关于如何调用语言模型的教程都会在 JSON.parse(response.content) 这行代码处结束。这段代码在处理前十个测试用例时确实可以有效运行。但当你开始实际应用时,会在第400次左右的一次请求中遇到问题:模型可能会返回一个它自己编造出来的日期,或者当你的数据结构应该包含5个元素时却返回8个数组项,又或者返回一个格式完全正确但实际上缺少某个字段的JSON对象。 我在开发Temploracraft这个简历工具时遇到了这样的问题。这个工具会接收用户上传的文档,并将其转换成应用程序可以编辑的结构化数据。 输入的数据确实具有很大的不可预测性:有些是两列结构的PDF文件,有些表格其实并

阅读全文
技术实践

从数据到价值:通过一个实际应用案例来理解数据管理[完整书籍]

如今,数据已成为一种极其宝贵的资源。它使企业能够在市场中展开竞争,并推动创新,从而提升所提供产品与服务的质量。 数据处理能让团队自动化各种流程。它还能辅助决策制定,为终端用户带来更加个性化的体验,在诸如银行欺诈防范或风险控制等诸多领域帮助人们发现其中的规律。企业必须明白如何以有效、安全且合法的方式收集和利用数据。 你很可能目前正在使用或曾经使用过各种各样的产品与服务。你也知道,那些涉及数据的流程几乎与我们身边的所有事物都息息相关。此外,你或许已经熟悉“大数据”、“数据分析”、“人工智能”以及“机器学习”这些术语了。 但除非你是这些领域中的专家,否则其中一些概念可能会让你感到难以理解。毕竟,这些

阅读全文
技术实践

如何使用Python和Neo4j构建知识图谱【完整指南】

你所处理的大部分数据实际上都反映了各种关系:一个客户属于某个账户,某次事件会影响某种服务,而一名工程师负责维护某个代码库。你把所有这些信息存储在表格中,长期以来,这种存储方式一直运行得非常顺利。 然而,有时会有人提出这样的问题: 哪些工程师最近了解过昨晚那次事件所影响的服务情况? 这类问题很容易理解,但编写相应的SQL查询却相当困难。通常需要使用四到五次连接操作,每次连接都会生成一个比最终结果范围更广的中间数据集,而大部分这些中间数据最终都会被丢弃。随着表格规模的扩大,查询速度会变得越来越慢,而且每次查看这个查询语句时,都很难理解其具体逻辑。 为了解决这类问题,人们才创造了图数据库。 在这本手

阅读全文
技术实践

OpenTelemetry的工作原理:一份全面的指南

如果你是一名软件开发人员或DevOps工程师,那么你很可能已经听说过OpenTelemetry。在讨论可观测性、监控或分布式系统的调试时,这个术语经常会被提及。 你可能也知道它的基本定义,但了解OpenTelemetry是什么与真正理解它的运作原理其实是两回事。 读完本指南后,你将能够明白OpenTelemetry是如何从端到端工作的——从请求进入你的应用程序的那一刻起,直到这些数据被显示在可观测性后端系统中。你还会了解到追踪信息、时间跨度、上下文传播机制以及数据导出工具是如何共同构成一个完整的处理流程的。 如果你完全不了解OpenTelemetry,也别担心:接下来的部分会帮助你快速掌握相关

阅读全文