<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nixon Perinchery</title>
    <description>The latest articles on DEV Community by Nixon Perinchery (@nixonperinchery).</description>
    <link>https://dev.to/nixonperinchery</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4208701%2F3dc40dc8-9e5d-4045-a6e7-7749199bc6a4.jpg</url>
      <title>DEV Community: Nixon Perinchery</title>
      <link>https://dev.to/nixonperinchery</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nixonperinchery"/>
    <language>en</language>
    <item>
      <title>Claude Code OpenTelemetry: Track Cost and Tool Use</title>
      <dc:creator>Nixon Perinchery</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:58:52 +0000</pubDate>
      <link>https://dev.to/nixonperinchery/claude-code-opentelemetry-track-cost-and-tool-use-c83</link>
      <guid>https://dev.to/nixonperinchery/claude-code-opentelemetry-track-cost-and-tool-use-c83</guid>
      <description>&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code exports usage metrics and events through OpenTelemetry once you set CLAUDE_CODE_ENABLE_TELEMETRY=1 and choose an exporter.&lt;/li&gt;
&lt;li&gt;A repository cannot switch telemetry on or point it somewhere: OTEL exporter variables in project settings files are ignored, so rollout belongs in managed settings.&lt;/li&gt;
&lt;li&gt;Prompt text, tool details and raw API bodies are off by default, and each has its own opt-in flag.&lt;/li&gt;
&lt;li&gt;Cost and token metrics can be split by model, subagent, skill, plugin and MCP server, which shows what drives your spend.&lt;/li&gt;
&lt;li&gt;The dollar figures are local estimates at list price (or your configured rates), not a bill. On Pro and Max plans usage is included in the subscription, so use the figures to compare, not to invoice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To monitor Claude Code usage across a team, turn on its built-in OpenTelemetry export: set &lt;code&gt;CLAUDE_CODE_ENABLE_TELEMETRY=1&lt;/code&gt;, pick an exporter, and point it at a collector. You then get token, cost, commit, pull request and tool-decision data in your own observability stack, tagged by model, subagent, skill and MCP server.&lt;/p&gt;

&lt;p&gt;The Claude Code docs describe OpenTelemetry export as working on every setup and as the only option that streams per-user token and cost metrics into your own stack in near real time. This guide covers the minimum setup, a managed-settings rollout, the privacy switches, and the questions the data can answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why export telemetry instead of reading /usage?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/usage&lt;/code&gt; is per machine. Its breakdown is computed from local session history, so usage from other devices or claude.ai is not included. That is fine for one developer and no help for a team that wants to compare weeks, repos or workflows.&lt;/p&gt;

&lt;p&gt;Telemetry sends the signals to one place. The docs' comparison for organizations lists the alternatives by setup: the spend report and analytics dashboard on Teams and Enterprise, the Console dashboard and Claude Code Analytics API for API customers, and your cloud's billing console on Amazon Bedrock, Google Cloud's Agent Platform or Microsoft Foundry. On the cloud providers, OpenTelemetry (or an LLM gateway) is how you get per-user numbers.&lt;/p&gt;

&lt;p&gt;One caveat up front: Claude Code computes dollar figures locally from token counts at list price. Pro and Max subscribers have usage included in the subscription, so a cost figure there is not a billing number. Even for API users the docs call it an estimate; the Console usage page is authoritative.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I turn on Claude Code OpenTelemetry?
&lt;/h2&gt;

&lt;p&gt;Set environment variables before starting Claude Code. These are the documented basics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_ENABLE_TELEMETRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1          &lt;span class="c"&gt;# required&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_METRICS_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;otlp              &lt;span class="c"&gt;# otlp, prometheus, console, none&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_LOGS_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;otlp                 &lt;span class="c"&gt;# otlp, console, none&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;grpc        &lt;span class="c"&gt;# grpc, http/json, http/protobuf&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4317
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Authorization=Bearer your-token"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no default protocol, so set &lt;code&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/code&gt; (or the per-signal protocol variable) for each OTLP exporter you enable. Metrics export every 60 seconds by default (&lt;code&gt;OTEL_METRIC_EXPORT_INTERVAL&lt;/code&gt;, in milliseconds) and logs every 5 seconds (&lt;code&gt;OTEL_LOGS_EXPORT_INTERVAL&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Test locally first
&lt;/h3&gt;

&lt;p&gt;Before you involve a collector, print metrics to the terminal at a one-second interval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_ENABLE_TELEMETRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_METRICS_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;console
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_METRIC_EXPORT_INTERVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or expose a Prometheus endpoint and scrape &lt;code&gt;http://localhost:9464/metrics&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_ENABLE_TELEMETRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_METRICS_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prometheus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To confirm a metrics pipeline works, look for &lt;code&gt;claude_code.session.count&lt;/code&gt;, which is emitted at session start. For a logs-only setup, submit a prompt and look for the &lt;code&gt;claude_code.user_prompt&lt;/code&gt; event. If nothing arrives, start Claude Code with &lt;code&gt;claude --debug-file &amp;lt;path&amp;gt;&lt;/code&gt; and search the file for &lt;code&gt;[3P telemetry]&lt;/code&gt; errors. Lines tagged &lt;code&gt;[Anthropic telemetry]&lt;/code&gt; describe Anthropic's own operational telemetry and do not point to a problem with your setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Send metrics and logs to different backends
&lt;/h3&gt;

&lt;p&gt;Per-signal variables override the generic ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_METRICS_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http/protobuf
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_METRICS_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://metrics.example.com:4318
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_LOGS_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;grpc
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_LOGS_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://logs.example.com:4317
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per-signal headers merge with the generic &lt;code&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/code&gt;. Metric temporality defaults to &lt;code&gt;delta&lt;/code&gt;; set &lt;code&gt;OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative&lt;/code&gt; if your backend expects that.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I roll it out to a whole team?
&lt;/h2&gt;

&lt;p&gt;Put the variables in the managed settings file so they apply to every user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CLAUDE_CODE_ENABLE_TELEMETRY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_METRICS_EXPORTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"otlp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_LOGS_EXPORTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"otlp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_EXPORTER_OTLP_PROTOCOL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_EXPORTER_OTLP_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://collector.example.com:4317"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"OTEL_EXPORTER_OTLP_HEADERS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization=Bearer example-token"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four behaviours matter here, all from the docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code ignores &lt;code&gt;OTEL_*&lt;/code&gt; exporter variables in &lt;code&gt;.claude/settings.json&lt;/code&gt; and &lt;code&gt;.claude/settings.local.json&lt;/code&gt;. A repository cannot enable telemetry, choose a destination or capture content. It can still set an exporter selector to &lt;code&gt;none&lt;/code&gt; to turn a signal off, unless managed settings, a &lt;code&gt;--settings&lt;/code&gt; file or the starting environment already sets that variable.&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/code&gt; in managed settings removes developer-set per-signal endpoints. Setting the protocol removes developer-set per-signal protocols. Setting headers or client key and certificate variables removes the developer-set per-signal versions of that variable and all developer-set endpoints.&lt;/li&gt;
&lt;li&gt;The exporter selectors (&lt;code&gt;OTEL_METRICS_EXPORTER&lt;/code&gt;, &lt;code&gt;OTEL_LOGS_EXPORTER&lt;/code&gt;, &lt;code&gt;OTEL_TRACES_EXPORTER&lt;/code&gt;) are not locked by those endpoint rules. To lock them, set them in managed settings too; the docs say this needs v2.1.223 or later.&lt;/li&gt;
&lt;li&gt;Claude Code does not pass &lt;code&gt;OTEL_*&lt;/code&gt; variables to subprocesses such as the Bash tool, hooks, MCP servers or language servers, so your own scripts do not inherit your collector credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first point is a useful safety property: a cloned repo cannot quietly redirect your telemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Claude Code measure?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Metrics
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;What it counts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.session.count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sessions, with &lt;code&gt;start_type&lt;/code&gt;: &lt;code&gt;fresh&lt;/code&gt;, &lt;code&gt;resume&lt;/code&gt;, &lt;code&gt;continue&lt;/code&gt;, &lt;code&gt;agents_view&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.lines_of_code.count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lines, &lt;code&gt;type&lt;/code&gt; &lt;code&gt;added&lt;/code&gt; or &lt;code&gt;removed&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.pull_request.count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.commit.count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Commits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.cost.usage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Estimated USD cost, per API request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.token.usage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens by &lt;code&gt;type&lt;/code&gt;: &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;, &lt;code&gt;cacheRead&lt;/code&gt;, &lt;code&gt;cacheCreation&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.code_edit_tool.decision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Accept or reject decisions for edit tools, with &lt;code&gt;tool_name&lt;/code&gt;, &lt;code&gt;decision&lt;/code&gt;, &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;language&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude_code.active_time.total&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Seconds, split into &lt;code&gt;user&lt;/code&gt; (keyboard) and &lt;code&gt;cli&lt;/code&gt; (tools and responses)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cost and token metrics also carry &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;query_source&lt;/code&gt; (&lt;code&gt;main&lt;/code&gt;, &lt;code&gt;subagent&lt;/code&gt; or &lt;code&gt;auxiliary&lt;/code&gt;), &lt;code&gt;speed&lt;/code&gt;, &lt;code&gt;effort&lt;/code&gt;, &lt;code&gt;agent.name&lt;/code&gt;, &lt;code&gt;skill.name&lt;/code&gt;, &lt;code&gt;plugin.name&lt;/code&gt;, &lt;code&gt;mcp_server.name&lt;/code&gt; and &lt;code&gt;mcp_tool.name&lt;/code&gt;. That attribution is the valuable part: it lets you ask whether spend comes from the main conversation, a subagent, one skill or one MCP server.&lt;/p&gt;

&lt;p&gt;Agent, skill, plugin and MCP names are redacted to &lt;code&gt;custom&lt;/code&gt; or &lt;code&gt;third-party&lt;/code&gt; unless &lt;code&gt;OTEL_LOG_TOOL_DETAILS=1&lt;/code&gt; is set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Events
&lt;/h3&gt;

&lt;p&gt;Events go through the logs exporter and share a &lt;code&gt;claude_code.&lt;/code&gt; prefix. The useful ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;api_request&lt;/code&gt;: model, &lt;code&gt;cost_usd&lt;/code&gt;, &lt;code&gt;duration_ms&lt;/code&gt;, token counts, &lt;code&gt;request_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api_error&lt;/code&gt;: &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;status_code&lt;/code&gt;, &lt;code&gt;attempt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool_result&lt;/code&gt;: &lt;code&gt;tool_name&lt;/code&gt;, &lt;code&gt;tool_use_id&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;duration_ms&lt;/code&gt;, &lt;code&gt;decision_source&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool_decision&lt;/code&gt;: &lt;code&gt;decision&lt;/code&gt; (&lt;code&gt;accept&lt;/code&gt; or &lt;code&gt;reject&lt;/code&gt;), &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;permission_mode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_prompt&lt;/code&gt; and &lt;code&gt;assistant_response&lt;/code&gt;: lengths by default, content only if you opt in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;prompt.id&lt;/code&gt; links every event from one prompt. &lt;code&gt;event.sequence&lt;/code&gt; is a per-process counter, so sort by &lt;code&gt;event.timestamp&lt;/code&gt; first. With those you can reconstruct what one instruction cost and which tools it touched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traces (beta)
&lt;/h3&gt;

&lt;p&gt;Set &lt;code&gt;CLAUDE_CODE_ENABLE_TELEMETRY=1&lt;/code&gt; and &lt;code&gt;CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1&lt;/code&gt;, then choose &lt;code&gt;OTEL_TRACES_EXPORTER&lt;/code&gt;. Spans run from &lt;code&gt;claude_code.interaction&lt;/code&gt;, the root span for each prompt, down to &lt;code&gt;claude_code.llm_request&lt;/code&gt; and &lt;code&gt;claude_code.tool&lt;/code&gt;, which has &lt;code&gt;blocked_on_user&lt;/code&gt; and &lt;code&gt;execution&lt;/code&gt; children. The blocked-on-user span shows how long work waited for a permission answer. The &lt;code&gt;claude_code.hook&lt;/code&gt; span needs extra settings (&lt;code&gt;ENABLE_BETA_TRACING_DETAILED=1&lt;/code&gt; and &lt;code&gt;BETA_TRACING_ENDPOINT&lt;/code&gt;), and interactive CLI sessions also need organization allowlisting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should I keep private?
&lt;/h2&gt;

&lt;p&gt;Content capture is off by default. Opt in deliberately:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_LOG_USER_PROMPTS=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prompt text in &lt;code&gt;user_prompt&lt;/code&gt;; otherwise &lt;code&gt;&amp;lt;REDACTED&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_LOG_ASSISTANT_RESPONSES=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Response text; follows the prompts flag if unset, &lt;code&gt;0&lt;/code&gt; keeps it redacted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_LOG_TOOL_DETAILS=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool parameters, Bash commands, MCP and skill names, full error messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_LOG_TOOL_CONTENT=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool output in a span event (needs tracing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;OTEL_LOG_RAW_API_BODIES=1&lt;/code&gt; or &lt;code&gt;file:&amp;lt;dir&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full API request and response bodies, inline or as untruncated files on disk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Treat the last one as sensitive data storage, since the bodies carry whatever the conversation contained. Content attributes are truncated at 60 KB by default (&lt;code&gt;CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH&lt;/code&gt;). A sensible team default is metrics plus event metadata, with tool details on only if you want per-skill and per-server attribution and your policy allows commands in logs.&lt;/p&gt;

&lt;p&gt;Standard attributes include &lt;code&gt;session.id&lt;/code&gt;, &lt;code&gt;user.account_uuid&lt;/code&gt;, &lt;code&gt;user.id&lt;/code&gt; (a random anonymous ID) and &lt;code&gt;user.email&lt;/code&gt;. If you do not want per-person data, check what your collector keeps.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I keep metric cardinality under control?
&lt;/h2&gt;

&lt;p&gt;Every attribute multiplies series in your backend. Defaults:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_METRICS_INCLUDE_SESSION_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_METRICS_INCLUDE_ACCOUNT_UUID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_METRICS_INCLUDE_RESOURCE_ATTRIBUTES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_METRICS_INCLUDE_VERSION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_METRICS_INCLUDE_ENTRYPOINT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OTEL_METRICS_INCLUDE_REPOSITORY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;false&lt;/code&gt; (v2.1.269 and later)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Session ID is the usual culprit, since every session creates a new series. If you only chart by team or model, set &lt;code&gt;OTEL_METRICS_INCLUDE_SESSION_ID=false&lt;/code&gt;. To add your own dimensions, use &lt;code&gt;OTEL_RESOURCE_ATTRIBUTES&lt;/code&gt;, for example &lt;code&gt;department=engineering,team.id=platform&lt;/code&gt;. Values cannot contain spaces, and quotes do not escape them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What questions can the data answer?
&lt;/h2&gt;

&lt;p&gt;Start with four panels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tokens by type over time.&lt;/strong&gt; A high &lt;code&gt;cacheCreation&lt;/code&gt; share next to low &lt;code&gt;cacheRead&lt;/code&gt; suggests sessions keep rebuilding context, for example after long idle gaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost by &lt;code&gt;agent.name&lt;/code&gt;, &lt;code&gt;skill.name&lt;/code&gt; and &lt;code&gt;mcp_server.name&lt;/code&gt;.&lt;/strong&gt; This shows whether a subagent or an MCP server dominates. Turn on &lt;code&gt;OTEL_LOG_TOOL_DETAILS=1&lt;/code&gt; to see real names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tool_decision&lt;/code&gt; rejects and &lt;code&gt;blocked_on_user&lt;/code&gt; time.&lt;/strong&gt; Many prompts waiting on a person point to permission rules that need tuning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;api_error&lt;/code&gt; by &lt;code&gt;status_code&lt;/code&gt;.&lt;/strong&gt; Rising errors or retries explain slow sessions better than guessing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use the same data to check changes. If you move a task type to a smaller model or lower effort, compare &lt;code&gt;claude_code.cost.usage&lt;/code&gt; split by &lt;code&gt;model&lt;/code&gt; and &lt;code&gt;effort&lt;/code&gt; before and after, rather than trusting a feeling.&lt;/p&gt;

&lt;p&gt;Where an organization pays contracted rates, the docs say cost figures, including OpenTelemetry, stay at list price unless an administrator sets the &lt;code&gt;modelPricing&lt;/code&gt; managed setting (Claude Code v2.1.242 or later). That changes what is reported, not what is charged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does Atlas fit?
&lt;/h2&gt;

&lt;p&gt;Atlas is a team of AI engineers built on Claude Code. Each engineer is its own Claude Code session, signed in with your own Claude Pro or Max plan, so there are no API keys and no per-token bill. Several engineers at once use the plan's limits faster, and Max gives the most room. Atlas has an optional daily budget that warns at 50% and 80% and pauses the team at 100%, and a Network screen shows what every engineer is doing live.&lt;/p&gt;

&lt;p&gt;Atlas does not export OpenTelemetry data today. Streaming the team's activity to a collector you own is coming to Atlas, built in, so it will need no setup on your side. Until then, if you run Claude Code yourself, the setup above works as written. Atlas is an independent product. Learn more at &lt;a href="https://bridge-terminal.com/atlas/?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=claude-code-opentelemetry-monitor-cost-usage" rel="noopener noreferrer"&gt;Atlas&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Telemetry is a small config change that gives you one place to see tokens, estimated cost and tool behaviour across everyone. Start with the console exporter on one machine, move to a collector, then enforce the settings through managed settings so a repository cannot change them. Keep prompt and API-body capture off unless you have a clear reason, and drop session ID from metrics if your backend struggles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/monitoring-usage" rel="noopener noreferrer"&gt;Claude Code OpenTelemetry reference (monitoring usage)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/costs" rel="noopener noreferrer"&gt;Manage costs effectively - Claude Code docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://bridge-terminal.com/atlas/blog/claude-code-opentelemetry-monitor-cost-usage/?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=claude-code-opentelemetry-monitor-cost-usage" rel="noopener noreferrer"&gt;Atlas blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>opentelemetry</category>
      <category>ai</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
