8000 feat(otel): attribute Claude usage to the account that sent each request for 0.12.0 by InfinityPacer · Pull Request #48 · InfinityPacer/meterleaf · GitHub
[go: up one dir, main page]

Skip to content

feat(otel): attribute Claude usage to the account that sent each request for 0.12.0 - #48

Merged
InfinityPacer merged 9 commits into
mainfrom
feat/otel-account-attribution
Oct 23, 2026
Merged

InfinityPacer merged 9 commits into
mainfrom
feat/otel-account-attribution

Conversation

@InfinityPacer
Copy link
Copy Markdown
Owner

问题

一台 Mac 上用多个 Claude 订阅账户时,默认账户用 ~/.claude,其他账户用各自的 CLAUDE_CONFIG_DIR 登录,会话目录 projects 软链接到一处,以便跨账户恢复会话。会话 JSONL 每行没有账户字段,采集器只能按「默认目录当前登录的账户」时间线归属,额外账户的用量会被记到默认账户名下。额外账户的套餐和额度也没有来源。

结果

  • 服务端新增 POST /api/otel/v1/logs,用采集器的写入密钥鉴权,接收 Claude Code 的 OpenTelemetry 日志(OTLP/HTTP JSON,可 gzip)。只保存 claude_code.api_request 中的请求 ID、账户、组织、会话、模型和请求时间,提示词等其他属性即使发来也不保存。
  • 服务端按 request_id 把同一请求的用量归到 OTEL 给出的账户,用量先到或映射先到都能对上。归属优先级是「模型映射 > OTEL > 登录时间线」,采集器推送的用量新增 metadata.attribution 来标明来源,模型映射的不会被覆盖。账户不存在时补一个占位账户,采集器推来的资料优先。
  • 采集器新增 claude-profiles,登记额外账户的配置目录,读取各自的账户、套餐(凭据两轮确认,默认账户的 meta 键名不变)、.claude.json 额度和状态栏额度。额外账户不进入默认账户的登录时间线。
  • 采集器新增 otel-headers,供 Claude Code 的 otelHeadersHelper 调用,写入密钥不必写进 settings.json。
  • 新增 request_accounts 表,数据库结构版本从 a17c9e4b620f 升到 c4f1a9d7e250。报表、累计和视图缓存库保持在基线版本。升级前要停服务并备份数据目录,先升级服务端,再升级采集器。
  • 升级后,采集器会把已推送的用量带上 attribution 重发一次(本机约 10.9 万条),之后照常增量推送。

验证

$ bun run typecheck   # 通过
$ bun test tests
 513 pass
 0 fail
Ran 513 tests across 70 files.

新增测试覆盖了鉴权和格式错误(401、415、400、413)、gzip、只保存规定字段、两种到达顺序、模型映射不被覆盖、nr: 键和 advisor 子事件、占位账户、报表账户分布、旧库迁移,以及额外账户的套餐、额度、时间线隔离和 otel-headers。

本机端到端:起一个隔离的本地服务端和采集器数据目录,用真实 Claude Code 2.1.301 开启 OTEL 导出,用额外账户(Pro)和默认账户(Team)各跑一次 -p 请求,然后让采集器读取这两个会话并推送。服务端两条用量分别归到额外账户和默认账户,额外账户套餐第二轮确认为 pro,额度来自它自己的状态栏缓存。

未运行:tests/compose.smoke.ts 需要 Docker,已按新的结构版本更新断言。

🤖 Generated with Claude Code

InfinityPacer and others added 9 commits October 23, 2026 20:02
Token usage still comes from the collector's JSONL reads. The new
OTLP/HTTP JSON endpoint records only the account identity of each
request, and the ledger rewrites matching usage facts in both arrival
orders unless the collector marked the account as model-mapped.

The ledger gains request_accounts in a new schema revision. Derived
stores stay on the baseline revision so they remain readable by
previous releases.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Extra profiles read their own CLAUDE_CONFIG_DIR for account, plan and
quota data without joining the default login timeline. Usage metadata
now records whether the account came from a model mapping or the
timeline, and an upgrade marker re-sends facts already pushed so the
server can apply request-level OTEL attribution.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Fastify raises FST_ERR_CTP_BODY_TOO_LARGE before the route handler, and
the shared error handler answered 500. OTLP exporters retry 5xx
indefinitely, so map the limit error to 413 for both ingest routes.

Co-Authored-By: Claude Code <noreply@anthropic.com>
A shell that exports CLAUDE_CONFIG_DIR for the account being registered
made that directory look like the default, so the command rejected the
intended profile. Compare with ~/.claude instead; registering the
collector's own custom default is harmless because the account is then
skipped as a duplicate.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The ledger moves to the new head while report, lifetime and view-cache
databases stay on the baseline, so the smoke check must assert each
expected pair instead of one shared revision.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@InfinityPacer InfinityPacer changed the title feat(otel): attribute Claude usage to the account that sent each request feat(otel): attribute Claude usage to the account that sent each request for 0.12.0 Oct 23, 2026
@InfinityPacer
Copy link
Copy Markdown
Owner Author

生产验收:候选镜像已在 NAS 上线(升级前停服并备份账本,迁移到 c4f1a9d7e250),本机采集器换成同一分支的构建,Claude Code 打开了 OTEL 导出。用真实的 Team、Pro 额外账户和 Claude Go 各跑一次会话,生产账本中三者分别归到 Team、Pro 和 Go 账户(Go 的用量保持按模型映射,没有被 OTEL 覆盖)。今天 Pro 账户被时间线误记到 Team 的 6 条请求,通过公网 OTEL 接口补发后已改回 Pro。采集器升级后重发了 108,834 条用量,没有报错。

@InfinityPacer
InfinityPacer merged commit 46a8b35 into main Oct 23, 2026
2 checks passed
@InfinityPacer
InfinityPacer deleted the feat/otel-account-attribution branch October 23, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0