Observability with OpenTelemetry changedagent-sdk/observability
Nearest release: v2.1.300, published 11 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 15 Oct 2026 01:28 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.
Upstream edited
Recorded here
Lines+128added
Lines−136removed
From line
13
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits11to this page, all time
The whole hunk
from line 13, old and new numbered
/
from line 13
1313
1414This guide explains how the SDK emits telemetry, how to configure the export, and how to tag and filter the data once it reaches your backend. To read token usage and cost directly from the SDK response stream instead of exporting to a backend, see [Track cost and usage](/docs/en/agent-sdk/cost-tracking).
1515
16<Note>
17 For the Claude API or Managed Agents, see [OpenTelemetry for the Claude API](https://platform.claude.com/docs/en/manage-claude/opentelemetry).
18</Note>
16> [!NOTE]
17>
18> For the Claude API or Managed Agents, see [OpenTelemetry for the Claude API](https://platform.claude.com/docs/en/manage-claude/opentelemetry).
1919
2020## How telemetry flows from the SDK
2121
from line 42
4242
4343The following example sets the variables in a dictionary and passes them through `options.env`. The agent runs a single task, and the CLI exports spans, metrics, and events to the collector at `collector.example.com` while the loop consumes the response stream:
4444
45<CodeGroup>
46 ```python Python theme={null}
47 import asyncio
48 from claude_agent_sdk import query, ClaudeAgentOptions
45```python Python theme={null}
46import asyncio
47from claude_agent_sdk import query, ClaudeAgentOptions
4948
50 OTEL_ENV = {
51 "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
52 # Required for traces, which are in beta. Metrics and log events do not need this.
53 "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
54 # Choose an exporter per signal. Use otlp for the SDK; see the Note below.
55 "OTEL_TRACES_EXPORTER": "otlp",
56 "OTEL_METRICS_EXPORTER": "otlp",
57 "OTEL_LOGS_EXPORTER": "otlp",
58 # Standard OTLP transport configuration.
59 "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
60 "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4318",
61 "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer your-token",
62 }
49OTEL_ENV = {
50 "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
51 # Required for traces, which are in beta. Metrics and log events do not need this.
52 "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
53 # Choose an exporter per signal. Use otlp for the SDK; see the Note below.
54 "OTEL_TRACES_EXPORTER": "otlp",
55 "OTEL_METRICS_EXPORTER": "otlp",
56 "OTEL_LOGS_EXPORTER": "otlp",
57 # Standard OTLP transport configuration.
58 "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
59 "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4318",
60 "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer your-token",
61}
6362
6463
65 async def main():
66 options = ClaudeAgentOptions(env=OTEL_ENV)
67 async for message in query(
68 prompt="List the files in this directory", options=options
69 ):
70 print(message)
64async def main():
65 options = ClaudeAgentOptions(env=OTEL_ENV)
66 async for message in query(
67 prompt="List the files in this directory", options=options
68 ):
69 print(message)
7170
7271
73 asyncio.run(main())
74 ```
72asyncio.run(main())
73```
7574
76 ```typescript TypeScript theme={null}
77 import { query } from "@anthropic-ai/claude-agent-sdk";
75```typescript TypeScript theme={null}
76import { query } from "@anthropic-ai/claude-agent-sdk";
7877
79 const otelEnv = {
80 CLAUDE_CODE_ENABLE_TELEMETRY: "1",
81 // Required for traces, which are in beta. Metrics and log events do not need this.
82 CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
83 // Choose an exporter per signal. Use otlp for the SDK; see the Note below.
84 OTEL_TRACES_EXPORTER: "otlp",
85 OTEL_METRICS_EXPORTER: "otlp",
86 OTEL_LOGS_EXPORTER: "otlp",
87 // Standard OTLP transport configuration.
88 OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
89 OTEL_EXPORTER_OTLP_ENDPOINT: "http://collector.example.com:4318",
90 OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer your-token",
91 };
78const otelEnv = {
79 CLAUDE_CODE_ENABLE_TELEMETRY: "1",
80 // Required for traces, which are in beta. Metrics and log events do not need this.
81 CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
82 // Choose an exporter per signal. Use otlp for the SDK; see the Note below.
83 OTEL_TRACES_EXPORTER: "otlp",
84 OTEL_METRICS_EXPORTER: "otlp",
85 OTEL_LOGS_EXPORTER: "otlp",
86 // Standard OTLP transport configuration.
87 OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
88 OTEL_EXPORTER_OTLP_ENDPOINT: "http://collector.example.com:4318",
89 OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer your-token",
90};
9291
93 for await (const message of query({
94 prompt: "List the files in this directory",
95 // env replaces the inherited environment in TypeScript, so spread
96 // process.env first to keep PATH, ANTHROPIC_API_KEY, and other variables.
97 options: { env: { ...process.env, ...otelEnv } },
98 })) {
99 console.log(message);
100 }
101 ```
102</CodeGroup>
92for await (const message of query({
93 prompt: "List the files in this directory",
94 // env replaces the inherited environment in TypeScript, so spread
95 // process.env first to keep PATH, ANTHROPIC_API_KEY, and other variables.
96 options: { env: { ...process.env, ...otelEnv } },
97})) {
98 console.log(message);
99}
100```
103101
104102Because the child process inherits your application's environment by default, you can achieve the same result by exporting these variables in a Dockerfile, Kubernetes manifest, or shell profile and omitting `options.env` entirely.
105103
106104To confirm that export is working, check your collector's logs for incoming spans, metrics, and log events after the task completes. The CLI fails silently on export errors by default: if the endpoint is unreachable or rejects the data, the agent still runs normally and the CLI drops the telemetry without surfacing an error in your application. To surface exporter errors, set [`CLAUDE_CODE_OTEL_DIAG_STDERR=1`](/docs/en/env-vars) alongside the exporter variables and read the diagnostics through the SDK's `stderr` callback (Python) or `stderr` option (TypeScript). Requires Claude Code v2.1.179 or later.
107105
108<Note>
109 The `console` exporter writes telemetry to standard output, which the SDK uses
110 as its message channel. Do not set `console` as an exporter value when running
111 through the SDK. To inspect telemetry locally, point
112 `OTEL_EXPORTER_OTLP_ENDPOINT` at a local OpenTelemetry Collector instead.
113</Note>
106> [!NOTE]
107>
108> The `console` exporter writes telemetry to standard output, which the SDK uses
109> as its message channel. Do not set `console` as an exporter value when running
110> through the SDK. To inspect telemetry locally, point
111> `OTEL_EXPORTER_OTLP_ENDPOINT` at a local OpenTelemetry Collector instead.
114112
115113### Flush telemetry from short-lived calls
116114
from line 116
118116
119117By default, metrics export every 60 seconds and traces and logs export every 5 seconds. The following example shortens all three intervals so that data reaches the collector while a short task is still running:
120118
121<CodeGroup>
122 ```python Python theme={null}
123 OTEL_ENV = {
124 # ... exporter configuration from the previous example ...
125 "OTEL_METRIC_EXPORT_INTERVAL": "1000",
126 "OTEL_LOGS_EXPORT_INTERVAL": "1000",
127 "OTEL_TRACES_EXPORT_INTERVAL": "1000",
128 }
129 ```
119```python Python theme={null}
120OTEL_ENV = {
121 # ... exporter configuration from the previous example ...
122 "OTEL_METRIC_EXPORT_INTERVAL": "1000",
123 "OTEL_LOGS_EXPORT_INTERVAL": "1000",
124 "OTEL_TRACES_EXPORT_INTERVAL": "1000",
125}
126```
130127
131 ```typescript TypeScript theme={null}
132 const otelEnv = {
133 // ... exporter configuration from the previous example ...
134 OTEL_METRIC_EXPORT_INTERVAL: "1000",
135 OTEL_LOGS_EXPORT_INTERVAL: "1000",
136 OTEL_TRACES_EXPORT_INTERVAL: "1000",
137 };
138 ```
139</CodeGroup>
128```typescript TypeScript theme={null}
129const otelEnv = {
130 // ... exporter configuration from the previous example ...
131 OTEL_METRIC_EXPORT_INTERVAL: "1000",
132 OTEL_LOGS_EXPORT_INTERVAL: "1000",
133 OTEL_TRACES_EXPORT_INTERVAL: "1000",
134};
135```
140136
141137## Read agent traces
142138
from line 147
151147
152148Spans carry a `session.id` attribute by default. When you make several `query()` calls against the same [session](/docs/en/agent-sdk/sessions), filter on `session.id` in your backend to see them as one timeline. Claude Code omits the attribute if you set `OTEL_METRICS_INCLUDE_SESSION_ID` to a falsy value.
153149
154<Note>
155 Tracing is in beta. Span names and attributes may change between releases. See
156 [Traces (beta)](/docs/en/monitoring-usage#traces-beta) in the Monitoring reference
157 for the trace exporter configuration variables.
158</Note>
150> [!NOTE]
151>
152> Tracing is in beta. Span names and attributes may change between releases. See
153> [Traces (beta)](/docs/en/monitoring-usage#traces-beta) in the Monitoring reference
154> for the trace exporter configuration variables.
159155
160156## Link traces to your application
161157
from line 169
173169
174170The following example renames the service and attaches deployment metadata. These values are applied as OpenTelemetry resource attributes on every span, metric, and event the agent emits:
175171
176<CodeGroup>
177 ```python Python theme={null}
178 options = ClaudeAgentOptions(
179 env={
180 # ... exporter configuration from the Enable telemetry export example ...
181 "OTEL_SERVICE_NAME": "support-triage-agent",
182 "OTEL_RESOURCE_ATTRIBUTES": "service.version=1.4.0,deployment.environment=production",
183 },
184 )
185 ```
186
187 ```typescript TypeScript theme={null}
188 const options = {
189 env: {
190 ...process.env,
191 // ... exporter configuration from the Enable telemetry export example ...
192 OTEL_SERVICE_NAME: "support-triage-agent",
193 OTEL_RESOURCE_ATTRIBUTES:
194 "service.version=1.4.0,deployment.environment=production",
172```python Python theme={null}
173options = ClaudeAgentOptions(
174 env={
175 # ... exporter configuration from the Enable telemetry export example ...
176 "OTEL_SERVICE_NAME": "support-triage-agent",
177 "OTEL_RESOURCE_ATTRIBUTES": "service.version=1.4.0,deployment.environment=production",
195178 },
196 };
197 ```
198</CodeGroup>
179)
180```
199181
182```typescript TypeScript theme={null}
183const options = {
184 env: {
185 ...process.env,
186 // ... exporter configuration from the Enable telemetry export example ...
187 OTEL_SERVICE_NAME: "support-triage-agent",
188 OTEL_RESOURCE_ATTRIBUTES:
189 "service.version=1.4.0,deployment.environment=production",
190 },
191};
192```
193
200194## Attribute actions to your end users
201195
202196The CLI attaches [identity attributes](/docs/en/monitoring-usage#standard-attributes) to every event based on the credential it uses to call Anthropic. When you build an application that serves many end users from one deployment, these attributes identify your service's credential, not the end user on whose behalf the agent acted.
from line 197
203197
204198To make tool calls and MCP activity attributable to your application's end users, inject end-user identity as resource attributes on each `query()` call. Percent-encode values before interpolating them, since `OTEL_RESOURCE_ATTRIBUTES` [reserves commas, spaces, and equals signs](/docs/en/monitoring-usage#multi-team-organization-support). The following example attaches the requesting user and tenant to every span and event from one request. It assumes a `request` object from your web framework carrying the user and tenant IDs:
205199
206<CodeGroup>
207 ```python Python theme={null}
208 from urllib.parse import quote
200```python Python theme={null}
201from urllib.parse import quote
209202
210 options = ClaudeAgentOptions(
211 env={
212 # ... exporter configuration from the Enable telemetry export example ...
213 # request is the incoming request object from your web framework.
214 "OTEL_RESOURCE_ATTRIBUTES": f"enduser.id={quote(request.user_id)},tenant.id={quote(request.tenant_id)}",
215 },
216 )
217 ```
218
219 ```typescript TypeScript theme={null}
220 const options = {
221 env: {
222 ...process.env,
223 // ... exporter configuration from the Enable telemetry export example ...
224 // request is the incoming request object from your web framework.
225 OTEL_RESOURCE_ATTRIBUTES: `enduser.id=${encodeURIComponent(request.userId)},tenant.id=${encodeURIComponent(request.tenantId)}`,
203options = ClaudeAgentOptions(
204 env={
205 # ... exporter configuration from the Enable telemetry export example ...
206 # request is the incoming request object from your web framework.
207 "OTEL_RESOURCE_ATTRIBUTES": f"enduser.id={quote(request.user_id)},tenant.id={quote(request.tenant_id)}",
226208 },
227 };
228 ```
229</CodeGroup>
209)
210```
211
212```typescript TypeScript theme={null}
213const options = {
214 env: {
215 ...process.env,
216 // ... exporter configuration from the Enable telemetry export example ...
217 // request is the incoming request object from your web framework.
218 OTEL_RESOURCE_ATTRIBUTES: `enduser.id=${encodeURIComponent(request.userId)},tenant.id=${encodeURIComponent(request.tenantId)}`,
219 },
220};
221```
230222
231223With end-user identity attached, the `tool_decision`, `tool_result`, `mcp_server_connection`, and `permission_mode_changed` events, which export as log records named with a `claude_code.` prefix, become a per-user audit trail you can forward to a Security Information and Event Management (SIEM) platform. See [Audit security events](/docs/en/monitoring-usage#audit-security-events) in the Monitoring reference for the full list of security-relevant events and the attributes each one carries.
232224
No line in this hunk matches that.