/blog/claude-code-opentelemetry
8 metrics / otlp native
free plan / 1 gb-mo
telemtra.io
// ai tooling telemetry

Monitor Claude Code
with OpenTelemetry.

Claude Code ships a native OpenTelemetry exporter: cost, tokens, sessions, and edit-acceptance metrics under claude_code.*, plus a structured event stream. Point it at any OTLP backend and AI spend becomes a dashboard instead of a monthly surprise.

AI coding assistants are the newest line item nobody's metering. If your team runs Claude Code, the good news is you don't need a vendor dashboard or a scraping hack to see what it's doing: Claude Code has a built-in OpenTelemetry exporter. Set a few environment variables and it emits metrics and structured events over OTLP to whatever backend already holds the rest of your telemetry. This post covers turning it on, what every metric means, and the handful of queries that make the data useful.

Everything here is opt-in — Claude Code exports nothing until you set CLAUDE_CODE_ENABLE_TELEMETRY=1.

Turn it on in two minutes

The fastest sanity check uses the console exporter, which prints metrics to your terminal every second instead of shipping them anywhere:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=console
export OTEL_METRIC_EXPORT_INTERVAL=1000   # ms; default is 60000
claude

Run a prompt or two and you'll see claude_code.token.usage and friends scroll by. For real use, switch to OTLP and point it at a collector or directly at a backend:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.telemtra.io
export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=YOUR_TELEMTRA_KEY"

Those OTEL_EXPORTER_OTLP_* variables are the standard OpenTelemetry SDK environment variables, not Claude-specific inventions — the same names configure any OTel SDK. grpc and http/json are also accepted as protocols; if you're unsure which transport to pick, the trade-offs are the same as anywhere else — see OTLP: HTTP vs gRPC. Verification is simple: look for the claude_code.session.count metric in your backend after starting a session.

The eight metrics

Claude Code exports eight metrics under the claude_code.* namespace. Every data point carries standard attributes — session.id, app.version, organization.id, user.email, terminal.type — plus per-metric attributes:

  • claude_code.cost.usage — estimated spend in USD, attributed by model and query source. The metric most teams turn on telemetry for.
  • claude_code.token.usage — token counts with a type attribute of input, output, cacheRead, or cacheCreation. The cacheRead-to-input ratio tells you whether prompt caching is actually working for your team.
  • claude_code.session.count — CLI sessions started, tagged with whether they're fresh, resumed, or continued.
  • claude_code.lines_of_code.count — lines added and removed (a type attribute), per model.
  • claude_code.code_edit_tool.decision — every accept or reject of a proposed edit, with the tool name and where the decision came from (user, config, hook). Team-level accept rate is the closest thing to a trust metric for AI-generated code.
  • claude_code.commit.count and claude_code.pull_request.count — commits and PRs created through Claude Code.
  • claude_code.active_time.total — seconds of real activity, split into user typing versus tool execution.

One export detail worth knowing before you build dashboards: metrics default to delta temporality. If your backend expects cumulative counters (Prometheus-lineage systems generally do), set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative — otherwise your rate queries will look wrong in confusing ways. The difference is covered in OpenTelemetry metrics in production. There's also a prometheus exporter option that serves a scrape endpoint on localhost:9464 if you'd rather pull than push.

The event stream

Alongside metrics, Claude Code emits structured log events (default flush every 5 seconds): claude_code.user_prompt when a prompt is submitted, claude_code.api_request with per-request cost, duration, and token counts, claude_code.api_error with status codes and retry attempts, claude_code.tool_result with tool name, success flag, and duration, and claude_code.tool_decision for permission decisions. The full event list — auth events, MCP server connections, plugin loads — is in the official monitoring docs.

Events are where debugging lives: a spike in claude_code.api_error with status_code=429 is a rate-limit problem, a fleet of slow tool_result events with tool_name=Bash is an environment problem. Metrics tell you something changed; events tell you what.

Privacy defaults worth knowing

Out of the box, the content of your work never leaves the machine. Prompt text, assistant responses, tool inputs and outputs are all excluded — the events carry lengths, durations, and metadata only. Each content category has an explicit opt-in (OTEL_LOG_USER_PROMPTS=1, OTEL_LOG_ASSISTANT_RESPONSES=1, OTEL_LOG_TOOL_DETAILS=1) that you should leave off unless you have a specific, agreed reason to turn it on. Claude Code also deliberately does not pass OTEL_* variables to subprocesses it spawns, so your telemetry config doesn't leak into build scripts and MCP servers.

Rolling it out to a team

Per-developer shell exports don't scale and don't survive laptop rebuilds. Put the config in a checked-in .claude/settings.json instead:

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://ingest.telemtra.io",
    "OTEL_RESOURCE_ATTRIBUTES": "team.id=platform,cost_center=eng-123"
  }
}

Two notes on this. First, keep the API key out of the checked-in file — use the otelHeadersHelper setting, which points at a script that returns headers as JSON and re-runs on an interval, so tokens can come from your secret manager and rotate without a config change. Second, OTEL_RESOURCE_ATTRIBUTES is the lever for organizational reporting: stamp team.id or cost_center onto every machine's exports and cost-per-team becomes a group-by. For org-wide enforcement, the same env block in managed settings locks the OTLP destination — developer-set overrides are dropped at startup.

If several developers export directly to a backend that's fine, but the usual advice applies: an OpenTelemetry Collector in the middle gives you batching, retry, and one place to filter before data hits your bill. Claude Code's volume is small compared to production services, so this is about hygiene, not cost — with one exception: session.id is a per-session attribute on every metric, and on a large team that's real cardinality. If you don't query per-session, set OTEL_METRICS_INCLUDE_SESSION_ID=false.

Five queries worth building

  • Daily spend by model — sum of claude_code.cost.usage grouped by model and day. Alert on a daily threshold; this is the "did someone leave an agent looping overnight" tripwire.
  • Cache hit ratio — token.usage{type="cacheRead"} over token.usage{type="input"}. A falling ratio usually means someone's workflow is invalidating prompt caches, which shows up on the bill weeks before anyone notices.
  • Edit accept rate — accepts over total from code_edit_tool.decision, grouped by team. A declining accept rate is an early signal that generated code quality dropped or the team is using the tool for the wrong tasks.
  • Cost per team — cost.usage grouped by your team.id resource attribute. This is the chart finance actually asks for.
  • API error rate — count of claude_code.api_error events by status_code. Distinguishes "Claude is slow today" from "we're hitting our rate limits."

Frequently asked questions

How do I enable telemetry in Claude Code?

CLAUDE_CODE_ENABLE_TELEMETRY=1 plus at least one exporter (OTEL_METRICS_EXPORTER=otlp, OTEL_LOGS_EXPORTER=otlp) and an endpoint. It's off by default; the working config is in the setup section above.

What metrics does Claude Code export?

Eight, under claude_code.*: sessions, lines of code, PRs, commits, cost in USD, tokens by type, edit accept/reject decisions, and active time. All attributed by model, session, and user.

Does Claude Code telemetry include my prompts or code?

No — content is redacted by default. Prompt text, responses, and tool inputs each require an explicit opt-in variable. Default exports are counts, costs, durations, and metadata.

Can I see cost per developer or per team?

Yes. cost.usage carries user.email on every point, and OTEL_RESOURCE_ATTRIBUTES adds team or cost-center tags fleet-wide.

How do I configure this for a whole team?

Check the env block into .claude/settings.json, keep tokens in an otelHeadersHelper script, and use managed settings if the destination must be enforced org-wide.

Further reading

The Claude Code monitoring docs are the authoritative reference for every variable and attribute. On the pipeline side, see the Collector quickstart for standing up the fan-in point, OpenTelemetry metrics in production for temporality and cardinality fundamentals, and how to avoid noisy observability bills for the filtering patterns. Start a Telemtra workspace if you want an OTLP endpoint to point Claude Code at in the next five minutes.