Use OpenTelemetry with the Claude SDK
Trace each request that the Claude SDK sends and each tool that its tool runner runs, and send the spans to your observability platform.
With OpenTelemetry, the Claude SDK can emit a span for each request that it sends and each tool use that the tool runner runs. You can view these spans in your observability platform, nested in your own traces.
The SDK emits spans in your application. Your application's OpenTelemetry exporter sends them to your observability platform.
1. Install the SDK
Install or upgrade to the latest version of the Claude SDK:
pip install --upgrade anthropicIf your application doesn't use the OpenTelemetry SDK yet, install it with a console exporter, which you use in the next step to print spans:
pip install opentelemetry-sdk2. Register a tracer provider
A tracer provider is the OpenTelemetry object that creates spans and passes them to your exporter. The SDK uses the tracer provider that your application registers, so your SDK calls stay the same. Register it before the first call that you want to trace.
The following example registers a tracer provider that prints each span to the console and then makes one call. Use it to see spans before you connect an observability platform:
from anthropic import Anthropic
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
# Register a tracer provider that prints each span to the console.
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
client = Anthropic()
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(message.content)The console shows one anthropic.messages.create span.
3. Send spans to your observability platform
Replace the console exporter with an OTLP exporter that points to your observability platform. For the setup in your language, see the OpenTelemetry docs on Python exporters, TypeScript exporters, .NET exporters, Go exporters, Java SDK configuration, PHP exporters, or Ruby exporters.
4. Configure the SDK
Use the following options to decide what goes into your spans and where they are sent. You can set an option in your code or, if it has one, with the environment variable under its name.
| Option | Use it to | Default |
|---|---|---|
traces.content_modeANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE=content | Include prompts and responses in spans. See Content modes. | metadata_only |
traces.max_content_bytesANTHROPIC_OPEN_TELEMETRY_TRACES_MAX_CONTENT_BYTES | Change the content limit of each span. See Content limits. | 0.75 MB |
conversation_id | Link several calls into one conversation. The SDK writes the ID to gen_ai.conversation.id. The ID is exported in both content modes, so don't put personal data or secrets in it. | None |
tracer_provider | Choose the tracer provider that gets the SDK's spans. | The tracer provider you set up in Register a tracer provider |
propagationANTHROPIC_OPEN_TELEMETRY_PROPAGATION=false | Stop sending trace context and baggage headers with requests, for example when your HTTP client instrumentation already sends them. The SDK still emits spans. Available in the Python, TypeScript, Go, Java, Ruby, and PHP SDKs. | On |
open_telemetryANTHROPIC_OPEN_TELEMETRY=false | Turn spans off for one client, for example when another library already traces the SDK. | On |
The following example shows how to use each option:
# Use a specific tracer provider and record content.
# Setting open_telemetry overrides all ANTHROPIC_OPEN_TELEMETRY* environment variables.
client = Anthropic(
open_telemetry={"tracer_provider": provider, "traces": {"content_mode": "content"}}
)
# Turn spans off for one client.
quiet_client = Anthropic(open_telemetry=False)
# Set a conversation ID with with_options(), which returns a copy of the client.
# with_options() replaces the whole option, so start from client.open_telemetry.
message = client.with_options(
open_telemetry={**client.open_telemetry, "conversation_id": "conv_42"}
).messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)Next steps
Get observability into the Claude API.
Follow a request from your client into the Claude API.
Tracing a stream
A stream's anthropic.messages.create span ends when your code has read the whole stream or closed it, so close a stream that you stop reading early. In content mode, the span has the model response only when you read the stream with messages.stream().
Spans that you start while you read a stream are siblings of the SDK span, not its children. To group them with the request, start your own span around both the call and the read loop.
Tracing a Managed Agents self-hosted sandbox
In a Managed Agents self-hosted sandbox, your sandbox runs the tools, so the tool spans come from the SDK instead of the API. The environment worker in the Python, TypeScript, and Go SDKs emits a span for each tool that it runs and each request that it sends. It groups them under one anthropic.environment.work span for each work item.
To get these spans:
- In the process that runs the worker, set up SDK tracing as in steps 1 to 4.
- Allow network traffic from the sandbox to your observability platform or OpenTelemetry Collector.
When the worker sends a tool result, that request's API span is nested under its SDK span. For requests that the worker sends to Claude Platform on AWS, the API spans are in their own traces instead.
Trace context and baggage
With each request, the SDK uses your application's propagator to send the trace context to the API in headers such as traceparent. If that propagator also writes baggage, the SDK sends your baggage too. To send the trace context without baggage, register only the W3C trace context propagator.
To stop the SDK from sending these headers, for example when your HTTP client instrumentation already sends them, turn off the SDK's propagation:
client = Anthropic(open_telemetry={"propagation": False})While the SDK calls an MCP tool, the SDK's tool span is the current span. Some MCP client libraries send the trace context to the MCP server in _meta, so a server that reads it can add its spans to your trace under that tool span. The Python mcp package from version 2.0 and the C# ModelContextProtocol package do this. The TypeScript, Go, Java, Ruby, and PHP MCP SDKs don't do it on their own.
Troubleshooting
| Problem | What to check |
|---|---|
| No spans from the SDK | Register the tracer provider before the first call. Make sure that you installed the SDK as in Install the SDK and that ANTHROPIC_OPEN_TELEMETRY isn't false. |
| The Java SDK doesn't send spans to the registered tracer provider, or doesn't send trace context to the API | Another dependency sets opentelemetry-api below 1.57.0. Upgrade opentelemetry-api to 1.57.0 or later. |
| The SDK spans don't nest under your spans, or the spans that your tool creates don't nest under the tool span | Call the SDK from inside one of your own spans, as the example in Connect API spans to your own traces does. In Go, start the spans that your tool creates from the ctx that the tool runner passes to it. |
Requests from the SDK don't have a traceparent header | Register the W3C trace context propagator with OpenTelemetry. The SDK writes the headers with your application's propagator. |
| A stream's SDK span doesn't end or doesn't have the response | See Tracing a stream. |
| Each request appears twice | A request's SDK span and API span have the same name. Use the span kind or service.name to tell them apart. If another library also traces the SDK, turn off one of the two. |
Compatibility
- Supported platforms
- Claude APIBeta
- Claude Platform on AWSBeta
- Amazon BedrockBeta
- Google CloudBeta
- Microsoft FoundryBeta
- Works with
- Messages API, Claude Managed Agents
Was this page helpful?