Skip to content
SpendByTeam

Journal How-to

Claude Code OpenTelemetry setup: a step-by-step guide

Turn on Claude Code's OpenTelemetry metrics the safe way: metrics only, logs and traces off, repository attribution, managed settings for the whole team, and a local collector to verify it works.

9 min read

Claude Code can report what it costs, how many tokens it uses and which repository the work happened in — through standard OpenTelemetry. It’s off by default, and the first time you switch it on it’s easy to send either too little (no repository, no session) or far too much (prompt text in your log pipeline).

This guide walks through a setup that is metrics only, attributes usage to repositories, rolls out to a whole team at once, and can be verified on your own machine before you point it at anything real.

What you’ll end up with

  • Usage metrics (cost, tokens, sessions, lines changed, commits, pull requests) exported over OTLP every minute
  • A session ID and repository on every data point
  • Logs and traces explicitly disabled, so no prompt or tool content leaves the machine
  • The same configuration on every developer’s machine, which they can’t redirect

Step 1: check the version

Repository attributes need Claude Code v2.1.269 or newer:

claude --version

Older versions still export metrics, but without the vcs.* attributes described in step 4. You’d then have to recover the repository some other way, such as a session hook. Upgrade first if you can.

Step 2: the environment block

All configuration is environment variables. Put them in the env block of a settings file rather than in shell profiles, so they apply however the agent is started:

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "none",
    "OTEL_TRACES_EXPORTER": "none",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://otel.example.com",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer YOUR_TOKEN",
    "OTEL_METRICS_INCLUDE_SESSION_ID": "true",
    "OTEL_METRICS_INCLUDE_REPOSITORY": "true",
    "OTEL_METRICS_INCLUDE_VERSION": "true",
    "OTEL_METRIC_EXPORT_INTERVAL": "60000"
  }
}

What each line does:

Variable Why
CLAUDE_CODE_ENABLE_TELEMETRY=1 Master switch. Nothing is exported without it.
OTEL_METRICS_EXPORTER=otlp Send metrics over OTLP.
OTEL_LOGS_EXPORTER=none Logs are event-level and can carry content. Off.
OTEL_TRACES_EXPORTER=none Not needed for usage reporting. Off.
OTEL_EXPORTER_OTLP_PROTOCOL=http/json Easy to debug; most collectors and vendors accept it.
OTEL_EXPORTER_OTLP_ENDPOINT Base URL. Metrics go to <endpoint>/v1/metrics.
OTEL_EXPORTER_OTLP_HEADERS Auth for your collector, as key=value pairs.
OTEL_METRICS_INCLUDE_SESSION_ID=true Adds session.id, so you can group by session.
OTEL_METRICS_INCLUDE_REPOSITORY=true Adds repository attributes (v2.1.269+).
OTEL_METRICS_INCLUDE_VERSION=true Adds app.version. Handy for spotting machines on old versions.
OTEL_METRIC_EXPORT_INTERVAL=60000 Export every 60 seconds.

Metrics only, on purpose

Set OTEL_LOGS_EXPORTER and OTEL_TRACES_EXPORTER to none explicitly rather than leaving them out. Being explicit documents the intent, and in a managed rollout it stops a developer’s local setting from switching them on.

Leave the content flags — OTEL_LOG_USER_PROMPTS, OTEL_LOG_TOOL_DETAILS and OTEL_LOG_RAW_API_BODIES — untouched. They’re off by default, and none of them is needed to measure cost. If someone asks to turn them on “just for debugging”, that’s a conversation for your security team, not a config change.

Step 3: where the settings go

There are three places the env block can live, and only two of them work for telemetry.

  1. User settings — ~/.claude/settings.json on each machine. Good for trying it out or for a single developer.
  2. Managed settings — a file deployed by IT or MDM, or server-managed settings pushed from your organization’s admin console on Team and Enterprise plans. The right choice for a team: one change, everyone covered.
  3. Project settings — .claude/settings.json inside a repository. This doesn’t work for the exporter. Repository-level settings can’t configure OpenTelemetry exporters — which also means a cloned repository can’t redirect your telemetry. Don’t commit telemetry config there and wonder why nothing arrives.

Managed settings have one more property that matters: when the endpoint and headers come from managed settings, Claude Code ignores any endpoint a developer sets locally. Data can’t quietly be sent somewhere else.

Cloud sessions are the exception to “it’s on everyone’s machine”: they don’t read your local ~/.claude/settings.json. Cover them with server-managed settings in the admin console.

Step 4: repository attribution

With OTEL_METRICS_INCLUDE_REPOSITORY=true on v2.1.269+, each data point carries:

  • vcs.repository.url.full — the remote URL
  • vcs.repository.name — e.g. checkout-api
  • vcs.owner.name — e.g. acme

These are resolved locally from the repository the session is working in, and are set per data point, so a session that moves between repositories is attributed correctly.

The same repository can show up under several URLs (git@github.com:acme/checkout-api.git, https://github.com/acme/checkout-api, a URL with credentials embedded). Normalise to host/owner/name before using it as a key. Our cost per project guide covers that, plus how to add branch and ticket with a hook.

If you want a team label on everything without maintaining a mapping, add a static resource attribute per group in managed settings:

"OTEL_RESOURCE_ATTRIBUTES": "team.id=mobile"

The value is static. Shell expressions like $(git config user.team) aren’t expanded.

Step 5: verify with a local collector

Before pointing a whole team at a backend, see the data with your own eyes. Save this as otel-collector.yaml — an OpenTelemetry Collector config with a debug exporter that prints everything it receives:

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
exporters:
  debug:
    verbosity: detailed
service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [debug]
docker run --rm -p 4318:4318 \
  -v "$PWD/otel-collector.yaml:/etc/otelcol-contrib/config.yaml" \
  otel/opentelemetry-collector-contrib:latest

Point your user settings at it temporarily, with a short interval so you don’t wait a minute per export:

"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
"OTEL_METRIC_EXPORT_INTERVAL": "10000"

Then start a new session inside a git repository and ask for something small. Settings are read at startup, so a session that was already running won’t pick up the change. Within a few seconds the collector should print metrics such as:

Metric #0
     -> Name: claude_code.cost.usage
     -> Unit: USD
     -> AggregationTemporality: Delta
NumberDataPoints #0
Data point attributes:
     -> session.id: Str(6f1c2a9e-…)
     -> model: Str(…)
     -> vcs.repository.name: Str(checkout-api)
     -> vcs.owner.name: Str(acme)
     -> vcs.repository.url.full: Str(git@github.com:acme/checkout-api.git)

Your checklist:

  • claude_code.cost.usage and claude_code.token.usage arrive
  • Temporality is Delta, so you can sum data points directly
  • session.id is present
  • vcs.* attributes are present (if not, check the version)
  • Nothing arrives on the logs or traces pipelines

Once that’s clean, restore the real endpoint and interval, and move the block into managed settings.

Step 6: what you can measure now

Metric Useful for
claude_code.cost.usage Spend by repository, team, developer, model
claude_code.token.usage Why something is expensive (split by type: input, output, cacheRead, cacheCreation)
claude_code.session.count Adoption
claude_code.lines_of_code.count Volume of accepted changes
claude_code.commit.count, claude_code.pull_request.count Output signals, with caveats — see cost per pull request
claude_code.active_time.total Time spent with the agent

Remember that cost.usage is an estimate from token counts and list prices. On seat-based plans, read it as the API-equivalent value of the usage, not cash paid.

Troubleshooting

Symptom Likely cause
Nothing arrives CLAUDE_CODE_ENABLE_TELEMETRY missing, settings in a project file, or the session started before the change
Metrics arrive without vcs.* Claude Code older than v2.1.269, OTEL_METRICS_INCLUDE_REPOSITORY not set, or the session isn’t in a git repository
404 from your endpoint The endpoint should be the base URL; the exporter adds /v1/metrics itself
401 / 403 Header format: Authorization=Bearer token (the = is between name and value)
Exporter errors about logs Logs exporter not set to none, so it’s trying to send somewhere

Or send it to us

If you’d rather not run a collector and build the dashboards, SpendByTeam is an OTLP endpoint that turns these metrics into spend by team, project and client. It accepts metrics only, drops logs and traces unread, and the installer writes exactly the configuration above. See pricing — it’s free for up to 3 developers.