Skip to content
telemetry.dev
Esc
↑↓navigate↵open⌘Jpreview
On this page

Anthropic

Trace Anthropic Messages API calls, token usage, tools, thinking, and streams from TypeScript or Python.

Wrap your Anthropic client, and each Messages API call you make with it becomes a span in telemetry.dev. Your calls and their return values don’t change.

To trace requests on Anthropic’s side instead, with no code changes, see Claude Platform. To use the Claude SDK’s own OpenTelemetry spans instead of this wrapper, see Claude SDK; don’t use the wrapper and those spans together.

What you get

  • Messages, system instructions, tool definitions, tool choice, model, and sampling parameters
  • Response IDs, response models, stop reasons, content blocks, errors, and time to first chunk
  • Input, output, cache-read, cache-write, and thinking token counts
  • Text, tool input, citations, thinking, and signatures rebuilt from stream deltas
  • Calls made through beta.messages, messages.parse(), and the SDK tool runner

Span input tokens include cache reads and cache writes, following the OpenTelemetry convention. Anthropic’s own input_tokens leaves them out. Anthropic reports no total token count, and the integration doesn’t calculate one.

Install

npm i @telemetry-dev/sdk @telemetry-dev/anthropic @anthropic-ai/sdk
pip install telemetry-dev-anthropic

The Python package also installs telemetry-dev and anthropic.

Language Requirements
TypeScript Node.js >=20.19.0, @telemetry-dev/sdk ^0.1.8, @anthropic-ai/sdk >=0.110.0 <1
Python Python >=3.10, telemetry-dev >=0.2.9, anthropic >=0.116,<2

Trace a call

Set your telemetry.dev project API key and your Anthropic key. Copy the telemetry.dev key from the project setup page; it starts with td_live_.

export TELEMETRY_DEV_API_KEY=td_live_...
export ANTHROPIC_API_KEY=sk-ant-...

Call init(), wrap the client, and use it as usual:

import Anthropic from "@anthropic-ai/sdk";
import { flush, init, shutdown } from "@telemetry-dev/sdk";
import { wrapAnthropic } from "@telemetry-dev/anthropic";

init({ serviceName: "anthropic-app" });
const anthropic = wrapAnthropic(new Anthropic());

try {
  const message = await anthropic.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 200,
    system: "You are a helpful bot",
    messages: [
      { role: "user", content: "Tell me a joke about OpenTelemetry" },
    ],
  });

  console.log(
    message.content
      .filter((block) => block.type === "text")
      .map((block) => block.text)
      .join("\n"),
  );
} finally {
  await flush();
  await shutdown();
}
import telemetry_dev
from anthropic import Anthropic
from telemetry_dev_anthropic import wrap_anthropic

telemetry_dev.init(service_name="anthropic-app")
client = wrap_anthropic(Anthropic())

try:
    message = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=200,
        system="You are a helpful bot",
        messages=[
            {"role": "user", "content": "Tell me a joke about OpenTelemetry"},
        ],
    )
    print("\n".join(block.text for block in message.content if block.type == "text"))
finally:
    telemetry_dev.flush()
    telemetry_dev.shutdown()

Run the script, then open the trace explorer. The trace has one chat claude-sonnet-4-6 generation span with the messages, the reply, and token usage.

Without an API key, init() turns telemetry off and the wrapped client works as before.

Instrument all clients

To trace every client without wrapping each one, call the global function once at startup:

import { instrumentAnthropic } from "@telemetry-dev/anthropic";

instrumentAnthropic();
from telemetry_dev_anthropic import instrument_anthropic

instrument_anthropic()

In TypeScript, global instrumentation patches the ES module build of the @anthropic-ai/sdk copy that this package resolves. It misses clients created with require(), which use the CommonJS build. It can also miss the Bedrock, Vertex, AWS, and Foundry clients, because their packages can install a separate copy of @anthropic-ai/sdk. Wrap those clients with wrapAnthropic().

API

TypeScript Python What it does
wrapAnthropic(client) wrap_anthropic(client) Instruments messages and beta.messages on one client and returns it. Wrapping the same client again has no effect. Python accepts synchronous and asynchronous clients.
instrumentAnthropic() instrument_anthropic() Instruments every client through the stable and beta Messages classes.
uninstrumentAnthropic() uninstrument_anthropic() Removes global instrumentation.

These functions take no options.

Traced calls

Each call produces one chat {model} generation span.

Anthropic call Notes
messages.create() Includes stream: true (stream=True in Python).
messages.stream() In TypeScript, the helper goes through create(). In Python, the span starts when you enter the context manager, because that is when the SDK opens the HTTP stream.
messages.parse() One span for each request.
beta.messages.create(), stream(), and parse() Beta features such as context management and fast mode produce the same spans as messages.
Tool runner: beta.messages.toolRunner() in TypeScript, beta.messages.tool_runner() in Python The runner sends one request for each model turn, and each turn gets its own span. To group the turns, run the tool runner inside an agent span.

The integration doesn’t trace messages.countTokens() (count_tokens() in Python), beta.messages.countTokens(), Message Batches, or other non-Messages endpoints.

In TypeScript, traced calls keep asResponse() and withResponse(). Calling either one also records the HTTP status code and the request-id header on the span. The withRawResponse namespace isn’t instrumented separately.

In Python, requests sent through with_raw_response or with_streaming_response aren’t traced. The integration skips any request whose X-Stainless-Raw-Response header asks for a raw response.

Streaming

A stream span stays open until your code reads the stream to the end, closes it, or the stream fails. In Python, leaving the messages.stream() context manager also ends it. A stream you never read never finishes its span. Stream events reach your code unchanged.

The span combines usage from message_start and message_delta, plus text, citations, tool-input JSON, thinking, and signature deltas. If the final tool input isn’t valid JSON, it stays as the raw string. The span records the time to the first event.

In TypeScript, aborting a request before you read its stream ends the span with an error.

Provider names

The span’s gen_ai.provider.name comes from the client class, including subclasses:

Client Provider
AnthropicBedrock, AnthropicBedrockMantle (and their Async variants in Python) aws.bedrock
AnthropicVertex (and AsyncAnthropicVertex) gcp.vertex_ai
Any other client, including Claude Platform on AWS and Microsoft Foundry clients anthropic

Captured content

Prompts and responses follow the core SDK’s captureInput, captureOutput, and mask settings (capture_input, capture_output, and mask in Python). Tools and tool_choice are part of the input, so turning input capture off removes them too. With output capture off, response IDs, models, stop reasons, usage, timing, and errors are still recorded.

The capture settings don’t cover stop sequences, metadata and attributes you pass yourself, or exception messages and stack traces. Redact those separately if they can hold sensitive data. See privacy for server-side redaction.

Send telemetry before exit

In a short script, call flush() and then shutdown() before the process exits, as in the example above. In serverless code, call flush() before the handler returns. In TypeScript you can pass a waitUntil function to init() instead. See the TypeScript SDK and Python SDK guides for details.

Last updated on October 18, 2026

Was this page helpful?