Skip to content
Last9
Book demo

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.

EventEmitted whenKey attributes
claude_code.user_promptUser submits a promptprompt.length, session.id, user.email
claude_code.api_requestClaude API respondsllm.usage.total_tokens, cost_usd, model, duration_ms
claude_code.api_errorAPI call failserror.message, http.status_code, retry_attempt
claude_code.tool_resultTool execution completestool.name, tool.success, duration_ms, bash.command
claude_code.tool_decisionPermission prompt resolvedtool.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.

MetricUnitKey attributes
claude_code.session.countcountsession.id, user.email, app.version
claude_code.cost.usageUSDmodel, user.email
claude_code.token.usagetokenstype (input/output/cacheRead/cacheCreation), model
claude_code.lines_of_code.countcounttype (added/removed)
claude_code.pull_request.countcountstandard attributes
claude_code.commit.countcountstandard attributes
claude_code.code_edit_tool.decisioncounttool_name, decision (accept/reject), source, language
claude_code.active_time.totalsecondstype (user/cli)

Traces (beta)

Claude Code v2.1.139+ exports OTel spans when both CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 and OTEL_TRACES_EXPORTER=otlp are set. Each user prompt starts a claude_code.interaction root span; API calls, tool executions, and hook runs become children.

SpanEmitted forKey attributes
claude_code.interactionOne per user promptsession.id, user.email, prompt.id
claude_code.llm_requestEach Anthropic API callgen_ai.request.model, gen_ai.response.id, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, agent_id, parent_agent_id, llm_request.context
claude_code.toolEach tool invocation (parent span)tool.name, prompt.id
claude_code.tool.blocked_on_userTime waiting on permission prompttool.name
claude_code.tool.executionActual tool worktool.name, success, duration_ms
claude_code.hookHook executions (requires detailed beta tracing)hook.name, hook.event

Subagent attribution. When the Task tool spawns a subagent, the subagent’s claude_code.llm_request spans carry:

  • agent_id — unique identifier of the subagent (absent on main-thread spans)
  • parent_agent_id — identifier of the agent that spawned this one (absent for main session and for direct children of main)
  • llm_request.contextinteraction for main-thread calls, standalone for subagent calls

Prerequisites

  1. Last9 account — Sign up at app.last9.io
  2. Claude Code — Installed and authenticated (claude --version should work)
  3. OTLP credentials — Get your endpoint and auth header from Integrations → OpenTelemetry

Setup

  1. 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>)
  2. Set environment variables

    Add the following to your shell profile (~/.zshrc, ~/.bashrc, or equivalent):

    # Required: enable Claude Code telemetry
    export CLAUDE_CODE_ENABLE_TELEMETRY=1
    # Export logs, metrics, and traces to Last9
    export OTEL_LOGS_EXPORTER=otlp
    export OTEL_METRICS_EXPORTER=otlp
    export OTEL_TRACES_EXPORTER=otlp
    # Last9 OTLP destination
    export 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 counters
    export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative
    # Required for traces (currently beta) — also enables subagent agent_id/parent_agent_id span attributes
    export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
    # Identify your sessions
    export OTEL_SERVICE_NAME="claude-code"
    export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=local,team=<your-team>"

    Then reload your shell:

    source ~/.zshrc
  3. 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
  4. 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

Configuration reference

Core

VariableDefaultDescription
CLAUDE_CODE_ENABLE_TELEMETRY0Set to 1 to enable all telemetry
OTEL_LOGS_EXPORTERnoneotlp to export logs to Last9
OTEL_METRICS_EXPORTERnoneotlp to export metrics to Last9
OTEL_TRACES_EXPORTERnoneotlp to export traces to Last9 (requires CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1)
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA0Set to 1 to enable trace export, plus agent_id/parent_agent_id span attributes (beta)
OTEL_EXPORTER_OTLP_ENDPOINTLast9 OTLP endpoint URL
OTEL_EXPORTER_OTLP_HEADERSAuthorization=Basic <token>
OTEL_SERVICE_NAMEclaude-codeService name tag on all signals
OTEL_RESOURCE_ATTRIBUTESComma-separated key=value resource tags

Logs

VariableDefaultDescription
OTEL_LOGS_EXPORT_INTERVAL5000Flush interval in milliseconds
OTEL_LOG_USER_PROMPTS0Set to 1 to include full prompt text
OTEL_LOG_TOOL_DETAILS0Set to 1 to include tool input parameters

Metrics

VariableDefaultDescription
OTEL_METRIC_EXPORT_INTERVAL60000Flush interval in milliseconds
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCEdeltadelta or cumulative
OTEL_METRICS_INCLUDE_SESSION_IDtrueInclude session.id label (increases cardinality)
OTEL_METRICS_INCLUDE_VERSIONfalseInclude app.version label
OTEL_METRICS_INCLUDE_ACCOUNT_UUIDtrueInclude 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.email to 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_request

Useful 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.

Shared schema with Cowork and Office Agents

Claude Cowork and Claude Office Agents reuse the Claude Code OTel event schema via the Claude Agent SDK. If you are sending telemetry from more than one of these products to Last9, events land in the same log stream and can be distinguished by:

  • service.nameclaude-code vs cowork vs office-agents
  • terminal.typecli for Claude Code, cowork for Cowork sessions
  • workspace.host_paths — only present in Cowork events

You can build a single Last9 dashboard covering all three products by grouping on service.name.


Troubleshooting

No logs in Last9 after running Claude Code

  • Confirm CLAUDE_CODE_ENABLE_TELEMETRY=1 is set in the same shell session where you run claudeexport does 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 (not none or console)
  • 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/json if 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.usage becomes claude_code_cost_usage_total

No traces appearing in Last9

  • Both CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 AND OTEL_TRACES_EXPORTER=otlp must be set. The beta flag alone does not export traces; the exporter flag alone does not enable the tracing pipeline
  • Verify in the shell that will launch claude: env | grep -E "CLAUDE_CODE_ENHANCED|OTEL_TRACES" should print both lines
  • Restart claude after editing your shell profile — env vars only load at process start
  • Traces flush in ~5 second batches; wait at least 10 seconds before querying
  • In Last9 Traces, filter service.name = claude-code. If you only see logs/metrics, the trace pipeline is not exporting

Subagent calls not appearing nested under the parent Task tool span

  • This is expected in Claude Code v2.1.142. Subagent claude_code.llm_request spans live in a separate trace with their own session.id
  • Use agent_id (present on subagent spans, absent on main thread) to identify subagent activity
  • Filter llm_request.context = standalone to isolate subagent LLM calls; interaction for main thread

401 / authentication errors

  • Verify the header format: Authorization=Basic <token> (no extra quotes, no Bearer prefix)
  • Regenerate the token from Integrations → OpenTelemetry if it has expired

Events appear but user.email is missing

  • user.email is only populated when authenticated with a user account (not API key only)
  • Run claude --version to confirm you are logged in

Please get in touch with us on Discord or Email if you have any questions.