Skip to content

Instantly share code, notes, and snippets.

@sit
Created April 28, 2026 21:26
Show Gist options
  • Select an option

  • Save sit/ab1dc2b26f69a9e3a31f64f36e122ae8 to your computer and use it in GitHub Desktop.

Select an option

Save sit/ab1dc2b26f69a9e3a31f64f36e122ae8 to your computer and use it in GitHub Desktop.
Validate Claude Code OTEL export with a local OpenTelemetry collector

Validating Claude Code OpenTelemetry export with a local collector

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 otelcol 0.150.1 at the time of writing)

Files

  • claude-settings.json — settings passed via claude --settings. Sets the required OTEL_* env vars and enables the traces beta.
  • otel-collector-config.yaml — minimal OTLP-in / debug exporter pipeline for traces, metrics, and logs.

Common gotchas

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

Run

# 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-collector

What you should see

Resource: 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 temporality
  • claude_code.cost.usage (USD)
  • claude_code.token.usage (tokens), split by type=input|output|cacheRead|cacheCreation

Logs / events (scope com.anthropic.claude_code.events)

  • claude_code.user_prompt (prompt=<REDACTED> unless OTEL_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)

References

{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_TRACES_EXPORTER": "otlp",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_METRIC_EXPORT_INTERVAL": "5000",
"OTEL_LOGS_EXPORT_INTERVAL": "2000",
"OTEL_TRACES_EXPORT_INTERVAL": "2000",
"OTEL_METRICS_INCLUDE_SESSION_ID": "true",
"OTEL_METRICS_INCLUDE_VERSION": "true",
"OTEL_METRICS_INCLUDE_ACCOUNT_UUID": "true"
}
}
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
telemetry:
logs:
level: info
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment