Skip to main content
Track Claude Code usage, costs, and tool activity across your organization by exporting telemetry data through OpenTelemetry (OTel). Claude Code exports metrics as time series data via the standard metrics protocol, events via the logs/events protocol, and optionally distributed traces via the traces protocol.

Quick start

Configure OpenTelemetry using environment variables:
To verify a setup that exports metrics, check your backend for the claude_code.session.count metric, which Claude Code emits when a session starts. To verify a logs-only setup, submit a prompt and check for the claude_code.user_prompt event. If nothing arrives, run claude --debug and check the debug log for OTel export errors. For full configuration options, see the OpenTelemetry specification.

Administrator configuration

Administrators can configure OpenTelemetry settings for all users through the managed settings file. See the settings precedence for more information about how settings are applied. Example managed settings configuration:
Claude Code doesn’t pass OTEL_* environment variables to the subprocesses it spawns, including the Bash tool, hooks, MCP servers, and language servers. An OpenTelemetry-instrumented application that you run through the Bash tool doesn’t inherit Claude Code’s exporter endpoint or headers, so set those variables directly in the command if that application needs to export its own telemetry.

How managed settings lock the OTLP destination

When you set an OTEL_EXPORTER_OTLP_* variable in managed settings, Claude Code removes conflicting developer-set variables at startup and logs a warning you can see with claude --debug. What it removes depends on which variable you set:
  • Endpoints: when you set OTEL_EXPORTER_OTLP_ENDPOINT, Claude Code removes every developer-set per-signal endpoint. Developers can’t point one signal at a different collector, so you don’t need to also set the per-signal endpoint variables in managed settings.
  • Protocols: when you set OTEL_EXPORTER_OTLP_PROTOCOL, Claude Code removes every developer-set per-signal protocol.
  • Credentials: when you set OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_CLIENT_KEY, or OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, Claude Code removes the developer-set per-signal versions of that variable, plus every developer-set endpoint variable, generic or per-signal, since those credentials would otherwise reach a collector the managed settings didn’t choose.
  • Exporter selectors: OTEL_METRICS_EXPORTER, OTEL_LOGS_EXPORTER, and the beta OTEL_TRACES_EXPORTER follow normal per-key precedence. A developer’s setting can still disable a signal or switch it to the console exporter, so set the selectors in managed settings too if you need them locked. Across admin sources, OTEL_LOGS_EXPORTER follows the telemetry unit while the other two selectors merge per key. Requires Claude Code v2.1.223 or later.
Claude Code doesn’t remove per-signal variables that you set in managed settings itself, so you can route one signal to a different collector by setting its variable there, as the SIEM example does. If you set a per-signal credential there, Claude Code removes the developer-set endpoint for that signal. This removal behavior changes where telemetry is delivered, not what Claude Code collects. Before v2.1.217, every variable followed per-key settings precedence independently, so a signal-specific endpoint set in user settings or the shell redirected that signal away from the managed collector.

Configuration details

Common configuration variables

These variables configure exporters, endpoints, and export behavior for all deployments. If you set a per-signal endpoint or protocol variable, such as OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, Claude Code uses it instead of the generic variable for that signal. If you set a per-signal headers variable, such as OTEL_EXPORTER_OTLP_METRICS_HEADERS, Claude Code merges it with the generic OTEL_EXPORTER_OTLP_HEADERS for that signal. On machines with managed settings, see How managed settings lock the OTLP destination for what Claude Code removes. For the http/protobuf and http/json protocols, Claude Code sends each export request with a Content-Length header. Before v2.1.212, Claude Code versions from v2.1.191 onward sent these requests with chunked transfer encoding; Azure Monitor and other endpoints that require a declared length rejected them with 411 Length Required or 400 errors.

mTLS authentication

How you configure client certificates for the OTLP exporter depends on the OTLP protocol in use for that signal, set via OTEL_EXPORTER_OTLP_PROTOCOL or the per-signal override. The same configuration applies to metrics, logs, and traces. For grpc, the OpenTelemetry SDK reads the standard OTLP variables directly, so existing configurations that set the per-signal metrics variables continue to work. On machines with managed settings, Claude Code may remove developer-set per-signal credentials and endpoints at startup.

Metrics cardinality control

The following environment variables control which attributes are included in metrics to manage cardinality: Lower cardinality generally means better performance and lower storage costs but less granular data for analysis.

Traces (beta)

Distributed tracing exports spans that link each user prompt to the API requests and tool executions it triggers, so you can view a full request as a single trace in your tracing backend. Tracing is off by default. To enable it, set both CLAUDE_CODE_ENABLE_TELEMETRY=1 and CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1, then set OTEL_TRACES_EXPORTER to choose where spans are sent. Traces reuse the common OTLP configuration for endpoint, protocol, headers, and mTLS. On machines with managed settings, Claude Code may remove developer-set per-signal credentials and endpoints at startup. Spans redact user prompt text, tool input details, and tool content by default. Set OTEL_LOG_USER_PROMPTS=1, OTEL_LOG_TOOL_DETAILS=1, and OTEL_LOG_TOOL_CONTENT=1 to include them. When tracing is active, Bash and PowerShell subprocesses automatically inherit a TRACEPARENT environment variable containing the W3C trace context of the active tool execution span. This lets any subprocess that reads TRACEPARENT parent its own spans under the same trace, enabling end-to-end distributed tracing through scripts and commands that Claude runs. When tracing is active and Claude Code is connected directly to the Anthropic API, each model request carries a W3C traceparent header set to the claude_code.llm_request span’s context, and the API’s traceresponse header is recorded as a span link. Together these connect Claude Code’s client-side spans to the server-side trace through any compliant intermediary. Outbound HTTP MCP requests carry traceparent the same way. The header is not sent to third-party providers. By default, the traceparent header on model and HTTP MCP requests is sent only when ANTHROPIC_BASE_URL is unset or points at the Anthropic API, since some proxies reject unrecognized headers. The subprocess TRACEPARENT variable is controlled by the same switch for consistency. If you run Claude Code through a custom ANTHROPIC_BASE_URL proxy and want trace context propagated, set CLAUDE_CODE_PROPAGATE_TRACEPARENT=1. In Agent SDK and non-interactive sessions started with -p, Claude Code also reads TRACEPARENT and TRACESTATE from its own environment when starting each interaction span. This lets an embedding process pass its active W3C trace context into the subprocess so Claude Code’s spans appear as children of the caller’s distributed trace. Interactive sessions ignore inbound TRACEPARENT to avoid accidentally inheriting ambient values from CI or container environments. The inbound trace context also applies to events. In Agent SDK and -p sessions with TRACEPARENT set, each OTLP event log record carries trace_id and span_id values that join it to your application’s trace, even when the traces exporter isn’t configured, so your logging backend can correlate events with the rest of the trace. A record emitted while an interaction is active carries the interaction span’s IDs, even when Claude Code emits it outside the span’s async context, such as in a permission prompt callback or for a record buffered during startup and exported later. A record emitted with no active interaction span carries the inbound TRACEPARENT IDs directly. Before v2.1.214, records emitted outside the span’s async context carried the inbound TRACEPARENT IDs instead of the span’s IDs. Before v2.1.212, event records emitted outside an active span didn’t carry trace_id or span_id.

Span hierarchy

Each user prompt starts a claude_code.interaction root span. API calls, tool calls, and hook executions are recorded as its children. Tool spans have two child spans of their own: one for the time spent waiting on a permission decision and one for the execution itself. When the Agent tool, or legacy Task tool, spawns a subagent, the subagent’s API and tool spans nest under the parent’s claude_code.tool span.
In Agent SDK and claude -p sessions, claude_code.interaction itself becomes a child of the caller’s span when TRACEPARENT is set in the environment.

Span attributes

Every span carries the standard attributes plus a span.type attribute matching its name. The tables below list the additional attributes set on each span. The llm_request, tool.execution, and hook spans set OpenTelemetry status ERROR when they record a failure; the other spans always end with status UNSET. claude_code.interaction claude_code.llm_request Each retry attempt is also recorded as a gen_ai.request.attempt span event with attempt and client_request_id attributes. claude_code.tool When OTEL_LOG_TOOL_CONTENT=1, this span also records a tool.output span event whose attributes contain the tool’s input and output bodies, truncated at the content limit (60 KB by default) per attribute. claude_code.tool.blocked_on_user claude_code.tool.execution claude_code.hook This span is emitted only when detailed beta tracing is active, which requires ENABLE_BETA_TRACING_DETAILED=1 and BETA_TRACING_ENDPOINT in addition to the trace exporter configuration above. In interactive CLI sessions, this also requires your organization to be allowlisted for the feature. Agent SDK and non-interactive -p sessions are not gated. It is not emitted when only CLAUDE_CODE_ENHANCED_TELEMETRY_BETA is set.