A small, reproducible setup that proves claude --settings claude-settings.json
emits traces, metrics, and logs to an OTLP endpoint. Useful when bringing up a
real backend (Honeycomb, Grafana Tempo, Datadog, etc.) — get the wiring right
against localhost first, then point OTEL_EXPORTER_OTLP_ENDPOINT at the real
target.
Verified against:
- Claude Code
2.1.119 ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector:latest(rendered as otelcol0.150.1at the time of writing)
claude-settings.json— settings passed viaclaude --settings. Sets the requiredOTEL_*env vars and enables the traces beta.otel-collector-config.yaml— minimal OTLP-in /debugexporter pipeline for traces, metrics, and logs.
The wire protocol is OTLP but the env var prefix is OTEL_. Typing
OLTP_* looks right and breaks everything silently — the SDK sees no exporter
configuration and drops the data. Likewise the traces beta flag is
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA (note: TELEMETRY, not TELEMTRY).
With OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf, the base endpoint
http://localhost:4318 is correct; Claude Code's SDK appends /v1/traces,
/v1/metrics, /v1/logs itself. Use 4317 if you switch to
OTEL_EXPORTER_OTLP_PROTOCOL=grpc.
By default, prompt text and tool I/O are redacted in spans (<REDACTED>).
Flip these on if you want the full content in the collector logs (note: this
puts plaintext prompts and tool output on disk):
OTEL_LOG_USER_PROMPTS=1
OTEL_LOG_TOOL_DETAILS=1
OTEL_LOG_TOOL_CONTENT=1
# 1. Start the collector (debug exporter prints everything to stdout)
docker run -d --name otel-test-collector \
-p 127.0.0.1:4317:4317 -p 127.0.0.1:4318:4318 \
-v "$PWD/otel-collector-config.yaml:/etc/otelcol/config.yaml" \
ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector:latest \
--config=/etc/otelcol/config.yaml
# 2. Sanity check the receiver (expect HTTP 200 from an empty protobuf body)
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
-X POST http://localhost:4318/v1/traces \
-H 'Content-Type: application/x-protobuf' --data-binary ''
# 3. Drive Claude with a tool-using prompt
claude --settings claude-settings.json -p \
"list the files in the current directory and report their sizes"
# 4. Inspect the collector log for spans, metrics, and events
docker logs otel-test-collector | less
# 5. Clean up
docker rm -f otel-test-collectorResource: service.name=claude-code, service.version=<your claude version>.
Traces (scope com.anthropic.claude_code.tracing)
claude_code.interaction (root, one per prompt)
├── claude_code.llm_request gen_ai.system=anthropic, model=...
└── claude_code.tool tool_name=Bash
├── claude_code.tool.blocked_on_user
└── claude_code.tool.execution success=true, duration_ms=...
There may also be a separate standalone claude_code.llm_request trace with
query_source=generate_session_title — Claude Code's session-title side
request, which is expected.
Metrics (scope com.anthropic.claude_code)
claude_code.session.count— Sum, monotonic, delta temporalityclaude_code.cost.usage(USD)claude_code.token.usage(tokens), split bytype=input|output|cacheRead|cacheCreation
Logs / events (scope com.anthropic.claude_code.events)
claude_code.user_prompt(prompt=<REDACTED>unlessOTEL_LOG_USER_PROMPTS=1)claude_code.api_request(token + cost breakdown)claude_code.tool_decision(decision=accept|reject,source=...)claude_code.tool_result(success,duration_ms, sizes)
- Claude Code monitoring docs: https://code.claude.com/docs/en/monitoring-usage
- OTel Collector quick start: https://opentelemetry.io/docs/collector/quick-start/