Claude Code
Send Claude Code session telemetry — prompts, tool calls, API costs, and errors — to Last9 via OpenTelemetry.
Claude Code emits structured telemetry for every developer session: prompts submitted, tools invoked, API calls made, and costs incurred. Routing this data to Last9 lets you analyze AI usage patterns, track per-user spend, audit tool decisions, and alert on error rates — all within your existing observability stack.
Claude Code exports three OpenTelemetry signal types:
- Logs — individual events per prompt, API call, tool execution, and error
- Metrics — aggregated counters for cost, tokens, sessions, and code edits
- Traces (beta) — span-based correlation linking a prompt to all its API calls and tool executions
What gets exported
Logs (events)
Claude Code exports five event types as OpenTelemetry log records. All share a prompt.id UUID that lets you reconstruct the full sequence for a single user interaction.
| Event | Emitted when | Key attributes |
|---|---|---|
claude_code.user_prompt | User submits a prompt | prompt.length, session.id, user.email |
claude_code.api_request | Claude API responds | llm.usage.total_tokens, cost_usd, model, duration_ms |
claude_code.api_error | API call fails | error.message, http.status_code, retry_attempt |
claude_code.tool_result | Tool execution completes | tool.name, tool.success, duration_ms, bash.command |
claude_code.tool_decision | Permission prompt resolved | tool.name, tool.decision (accept/reject), decision.source |
Metrics
Claude Code exports eight counters as OpenTelemetry metrics. These are aggregated — no per-prompt IDs — making them low-cardinality and suitable for dashboards and alerts.
| Metric | Unit | Key attributes |
|---|---|---|
claude_code.session.count | count | session.id, user.email, app.version |
claude_code.cost.usage | USD | model, user.email |
claude_code.token.usage | tokens | type (input/output/cacheRead/cacheCreation), model |
claude_code.lines_of_code.count | count | type (added/removed) |
claude_code.pull_request.count | count | standard attributes |
claude_code.commit.count | count | standard attributes |
claude_code.code_edit_tool.decision | count | tool_name, decision (accept/reject), source, language |
claude_code.active_time.total | seconds | type (user/cli) |
Prerequisites
- Last9 account — Sign up at app.last9.io
- Claude Code — Installed and authenticated (
claude --versionshould work) - OTLP credentials — Get your endpoint and auth header from Integrations → OpenTelemetry
Setup
-
Get your Last9 OTLP credentials
Navigate to Integrations → OpenTelemetry in your Last9 dashboard. Copy:
- OTLP Endpoint (e.g.,
https://otlp-aps1.last9.io:443) - Authorization header (e.g.,
Basic <base64-token>)
- OTLP Endpoint (e.g.,
-
Set environment variables
Add the following to your shell profile (
~/.zshrc,~/.bashrc, or equivalent):# Required: enable Claude Code telemetryexport CLAUDE_CODE_ENABLE_TELEMETRY=1# Export both logs and metrics to Last9export OTEL_LOGS_EXPORTER=otlpexport OTEL_METRICS_EXPORTER=otlp# Last9 OTLP destinationexport OTEL_EXPORTER_OTLP_ENDPOINT="https://<your-last9-otlp-endpoint>"export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <your-last9-auth-token>"export OTEL_EXPORTER_OTLP_PROTOCOL=http/json# Required for metrics: Last9 expects cumulative countersexport OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative# Required for traces: currently in betaexport CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1# Identify your sessionsexport OTEL_SERVICE_NAME="claude-code"export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=local,team=<your-team>"export CLAUDE_CODE_ENABLE_TELEMETRY=1export OTEL_LOGS_EXPORTER=otlpexport OTEL_EXPORTER_OTLP_ENDPOINT="https://<your-last9-otlp-endpoint>"export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <your-last9-auth-token>"export OTEL_EXPORTER_OTLP_PROTOCOL=http/jsonexport OTEL_SERVICE_NAME="claude-code"export CLAUDE_CODE_ENABLE_TELEMETRY=1export OTEL_METRICS_EXPORTER=otlpexport OTEL_EXPORTER_OTLP_ENDPOINT="https://<your-last9-otlp-endpoint>"export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <your-last9-auth-token>"export OTEL_EXPORTER_OTLP_PROTOCOL=http/jsonexport OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulativeexport OTEL_SERVICE_NAME="claude-code"Then reload your shell:
source ~/.zshrc -
Start a Claude Code session
claude "summarize what this repo does"- Logs flush within 5 seconds of each event
- Metrics flush every 60 seconds by default
-
Verify data is arriving
- Logs — navigate to Logs in Last9, filter by
service.name = claude-code - Metrics — navigate to Metrics, search for
claude_code_cost_usage_total
- Logs — navigate to Logs in Last9, filter by
Configuration reference
Core
| Variable | Default | Description |
|---|---|---|
CLAUDE_CODE_ENABLE_TELEMETRY | 0 | Set to 1 to enable all telemetry |
OTEL_LOGS_EXPORTER | none | otlp to export logs to Last9 |
OTEL_METRICS_EXPORTER | none | otlp to export metrics to Last9 |
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA | 0 | Set to 1 to enable trace export (beta) |
OTEL_EXPORTER_OTLP_ENDPOINT | — | Last9 OTLP endpoint URL |
OTEL_EXPORTER_OTLP_HEADERS | — | Authorization=Basic <token> |
OTEL_SERVICE_NAME | claude-code | Service name tag on all signals |
OTEL_RESOURCE_ATTRIBUTES | — | Comma-separated key=value resource tags |
Logs
| Variable | Default | Description |
|---|---|---|
OTEL_LOGS_EXPORT_INTERVAL | 5000 | Flush interval in milliseconds |
OTEL_LOG_USER_PROMPTS | 0 | Set to 1 to include full prompt text |
OTEL_LOG_TOOL_DETAILS | 0 | Set to 1 to include tool input parameters |
Metrics
| Variable | Default | Description |
|---|---|---|
OTEL_METRIC_EXPORT_INTERVAL | 60000 | Flush interval in milliseconds |
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE | delta | delta or cumulative |
OTEL_METRICS_INCLUDE_SESSION_ID | true | Include session.id label (increases cardinality) |
OTEL_METRICS_INCLUDE_VERSION | false | Include app.version label |
OTEL_METRICS_INCLUDE_ACCOUNT_UUID | true | Include user.account_uuid label |
What you can do in Last9
Cost and spend tracking (metrics)
claude_code.cost.usage is a counter broken down by model and user.email. In Last9 Metrics, query the rate to get spend per minute, or sum it over a time window for daily/weekly totals:
- Dashboard total team spend across all models
- Break down by
user.emailto see per-developer cost - Alert when cumulative cost crosses a budget threshold
Token efficiency (metrics)
claude_code.token.usage tracks input, output, cache read, and cache creation tokens separately. Compare cacheRead / input ratio to measure prompt cache efficiency — a high ratio means less spend per interaction.
Session replay via prompt.id (logs)
Every log event shares a prompt.id UUID. Filter Last9 Logs by a specific prompt.id to reconstruct the full sequence for one user interaction:
user_prompt → api_request → tool_decision → tool_result → api_requestUseful for debugging slow sessions or unexpected tool rejections.
Tool usage audit (logs)
claude_code.tool_decision events record every permission decision with tool.decision = accept | reject and decision.source. Surface which tools are most frequently rejected and whether your --allowedTools policies are working.
Code output tracking (metrics)
claude_code.lines_of_code.count and claude_code.commit.count let you measure developer output attributable to Claude Code sessions — useful for adoption reporting.
Error rate monitoring (logs + alerts)
claude_code.api_error events include http.status_code and retry_attempt. Create a Last9 alert on error rate spikes to catch Claude API degradation before users report it.
Team-level tagging
For organizations with multiple teams, use OTEL_RESOURCE_ATTRIBUTES to tag sessions by team or project:
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,team=platform,project=infra-agent"All signals from that session carry team and project labels, enabling per-team cost breakdowns in Last9.
Troubleshooting
No logs in Last9 after running Claude Code
- Confirm
CLAUDE_CODE_ENABLE_TELEMETRY=1is set in the same shell session where you runclaude—exportdoes not persist across tabs or new sessions - Set
OTEL_EXPORTER_OTLP_PROTOCOL=http/json— Last9’s endpoint requires HTTP, not gRPC (the OTel SDK default) - Check that
OTEL_LOGS_EXPORTER=otlp(notnoneorconsole) - Wait at least 10 seconds — the default export interval is 5s and there may be one flush delay
No metrics appearing
- Set
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative— Claude Code defaults to delta temporality but Last9 requires cumulative counters - Set
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/jsonif the global protocol var is not being picked up - Metrics flush every 60 seconds by default — wait at least 90 seconds before checking
- Metric names in Last9 use underscores:
claude_code.cost.usagebecomesclaude_code_cost_usage_total
401 / authentication errors
- Verify the header format:
Authorization=Basic <token>(no extra quotes, noBearerprefix) - Regenerate the token from Integrations → OpenTelemetry if it has expired
Events appear but user.email is missing
user.emailis only populated when authenticated with a user account (not API key only)- Run
claude --versionto confirm you are logged in
Please get in touch with us on Discord or Email if you have any questions.