Skip to content
telemetry.dev
Esc
navigateopen⌘Jpreview
On this page

Python SDK

Add spans, logs, metrics, and trace context to Python applications.

telemetry-dev is the core Python SDK. It sends traces, logs, and metrics through OTLP/HTTP.

Use Python 3.10 or later.

Install

pip install telemetry-dev
uv add telemetry-dev

Make a project at telemetry.dev. Then copy an API key from the project setup page. Keys look like td_live_....

TELEMETRY_DEV_API_KEY=td_live_...
OTEL_SERVICE_NAME=my-service
TELEMETRY_DEV_ENVIRONMENT=production

Start the SDK

Call telemetry_dev.init() one time when the application starts. This verified example uses decorators, context managers, logs, and propagated attributes:

import telemetry_dev
from telemetry_dev import log, observe, propagate_attributes, start_span, update_current_span

telemetry_dev.init()  # reads TELEMETRY_DEV_API_KEY

@observe  # args -> input, return -> output, errors captured + re-raised
def plan_route(city: str) -> dict[str, str]:
    log("planning route", attributes={"city": city})
    return {"city": city, "route": "scenic"}

with propagate_attributes(user_id="user_123", session_id="session_456"):
    with start_span(
        "chat gpt-4o",
        type="generation",
        model="gpt-4o",
        provider="openai",
        input=[{"role": "user", "content": "Plan a day trip"}],
    ):
        update_current_span(
            output=[{"role": "assistant", "content": "Here you go..."}],
            usage={"input_tokens": 11, "output_tokens": 7},
            finish_reason="stop",
        )
    plan_route("Kyoto")

telemetry_dev.flush()
telemetry_dev.shutdown()

init() returns a Client. The client and module functions have the same flush() and shutdown() lifecycle operations.

init() keyword arguments

An explicit argument has precedence over an environment variable.

Argument Type Environment fallback Default Purpose
api_key str | None TELEMETRY_DEV_API_KEY None Authenticates exports. Without a key, the SDK is a no-op.
base_url str | None TELEMETRY_DEV_BASE_URL https://ingest.telemetry.dev Sets the ingest base URL. The SDK removes trailing slashes.
environment str | None TELEMETRY_DEV_ENVIRONMENT production Sets deployment.environment.name.
service_name str | None OTEL_SERVICE_NAME unknown_service Sets service.name.
enabled bool None True Deactivates the client when set to False.
register_global bool None False Registers the client tracer provider as the global provider.
export_mode "batched" | "immediate" None "batched" Selects batch export or immediate export for spans and logs.
log_level "debug" | "info" | "warn" | "error" | "silent" None "warn" Controls SDK diagnostic output.
capture_input bool None True Sets the default input capture.
capture_output bool None True Sets the default output capture.
mask Callable[[Any, MaskContext], Any] | None None None Changes captured content before serialization.
max_attribute_length int None 65536 Limits each captured content attribute.
span_filter Callable[[ReadableSpan], bool] | None None None Selects spans at export time.
on_error Callable[[BaseException], None] | None None None Receives internal SDK errors.
disable_atexit bool None False Prevents registration of the automatic shutdown hook.
timeout float None 10.0 Sets the OTLP HTTP timeout in seconds.
span_exporter SpanExporter | None None None Supplies a span exporter test seam.
log_exporter LogRecordExporter | None None None Supplies a log exporter test seam.
metric_reader MetricReader | None None None Supplies a metric reader test seam.

One exporter or reader seam activates the client without an API key. Without a key or seam, the SDK does not make network exporters.

A second init() call shuts down and replaces the first client.

Observe functions

Use @observe without arguments or with configuration:

from telemetry_dev import observe

@observe
def load_account(account_id: str):
    return repository.get(account_id)

@observe(
    name="run-tool",
    type="tool",
    capture_input=False,
    capture_output=True,
    attributes={"component": "account-store"},
)
def run_tool(payload):
    return tool.execute(payload)

The decorator accepts name, type, capture_input, capture_output, and attributes. It operates with synchronous and asynchronous functions.

The decorator records arguments as a parameter-name dictionary. It removes self and cls. It records the return value as output.

The decorator activates the span, so child spans use it as their parent. It records and rethrows exceptions.

Create spans

start_span() returns a SpanHandle. Use it as a context manager to activate the span:

from telemetry_dev import start_span

with start_span(
    "model-call",
    type="generation",
    model="gpt-4o",
    provider="openai",
    input=messages,
) as span:
    response = call_model(messages)
    span.update(
        output=response.message,
        usage={"input_tokens": 12, "output_tokens": 8},
        finish_reason="stop",
    )

A bare start_span() call returns a detached handle. End that handle explicitly:

span = start_span("background-work")
try:
    do_work()
    span.end()
except Exception as error:
    span.end(error=error)
    raise

update_current_span(**fields) changes the active span. It is a no-op when no span is active.

SpanHandle

A SpanHandle has these operations:

  • update(**fields) changes fields and returns the same handle.
  • end(**fields, end_time=None) applies final fields and ends the span.
  • traceparent() returns a W3C traceparent or None.

Span types

type Operation Input attribute Output attribute
"span" function gen_ai.input.messages gen_ai.output.messages
"generation" chat gen_ai.input.messages gen_ai.output.messages
"tool" execute_tool gen_ai.tool.call.arguments gen_ai.tool.call.result
"agent" invoke_agent gen_ai.input.messages gen_ai.output.messages
"embedding" embeddings gen_ai.input.messages gen_ai.output.messages

The default type is "span".

Span fields

start_span(), SpanHandle.update(), and SpanHandle.end() accept the same field set:

Group Fields
Content input, output, system_instructions
Model model, provider, response_model, response_id, output_type, finish_reason
Usage and cost usage, cost_usd
Model request temperature, top_p, top_k, max_tokens, stop_sequences, seed, frequency_penalty, presence_penalty
Stream latency time_to_first_chunk_ms
Tool tool_name, tool_call_id, tool_description
Agent agent_name, agent_id
Other name, metadata, attributes, error

start_span() also accepts parent, start_time, capture_input, and capture_output. parent accepts a traceparent string, OpenTelemetry Context, or SpanContext.

end() also accepts end_time.

metadata uses the td.metadata.* prefix. The SDK drops userId, sessionId, user_id, and session_id from metadata.

attributes merge last and can replace mapped attributes. time_to_first_chunk_ms becomes seconds in gen_ai.response.time_to_first_chunk.

An error sets OpenTelemetry error status and error.type. It also adds an exception event with the stack trace.

Usage

The exported Usage type and usage dictionaries accept six keys:

Key Attribute
input_tokens gen_ai.usage.input_tokens
output_tokens gen_ai.usage.output_tokens
total_tokens gen_ai.usage.total_tokens
cache_read_input_tokens gen_ai.usage.cache_read.input_tokens
cache_creation_input_tokens gen_ai.usage.cache_creation.input_tokens
reasoning_output_tokens gen_ai.usage.reasoning.output_tokens

The SDK drops unknown usage keys and writes a debug diagnostic.

Propagate user, session, and metadata attributes

propagate_attributes() is a context manager. It adds correlation attributes to each span and log in its scope:

from telemetry_dev import propagate_attributes

with propagate_attributes(
    user_id="user_123",
    session_id="session_456",
    metadata={"tenant": "acme"},
):
    handle_request()

The SDK maps user_id to user.id and session_id to gen_ai.conversation.id. It maps metadata to td.metadata.*.

Nested scopes combine attributes. Inner values replace outer values with the same key. The context uses contextvars.

get_traceparent() returns the W3C traceparent for the current context, or None.

Send logs

log() sends an OpenTelemetry log in the active trace context:

from telemetry_dev import log

log(
    "model request complete",
    level="info",
    event_name="model.complete",
    attributes={"attempt": 1, "cached": False},
)
Argument Type Default
message str Required
level "debug" | "info" | "warn" | "error" "info"
event_name str | None None
attributes dict[str, Any] | None None

The alias "warning" becomes "warn". An unknown level becomes "info".

The SDK applies masking and truncation to the log body. Propagated attributes replace caller attributes when the keys are equal.

Control captured content

Input and output capture are active by default. Set capture_input or capture_output globally, on @observe, or on start_span().

from telemetry_dev import init


def mask(value, _context):
    return "[redacted]"


init(
    capture_input=True,
    capture_output=False,
    mask=mask,
    max_attribute_length=16_384,
)

The SDK applies the capture sequence to input, output, system instructions, and log bodies:

  1. The mask function changes the value.
  2. The SDK serializes the value with JSON.
  3. The SDK truncates the string to max_attribute_length.

A truncated value ends with ...[truncated]. The SDK counts UTF-16 code units to match the TypeScript SDK.

If mask raises an error, the SDK drops the value and sends the error to on_error.

Provider stream capture uses CaptureBudget. Its defaults are 65,536 bytes, 1,024 items, and a maximum depth of 32.

The byte and item limits also have hard ceilings of 64 KiB and 1,024 items. The active client supplies max_attribute_length to the budget.

See Privacy for server-side capture and redaction controls.

Global OpenTelemetry registration and filtering

The SDK uses an isolated tracer provider by default. Set register_global=True when other instrumentation must use this provider.

With global registration, the default filter exports only spans from the telemetry_dev instrumentation scope. Use a filter that returns True to export all spans:

telemetry_dev.init(
    register_global=True,
    span_filter=lambda span: True,
)

A span_filter error does not drop the span. The SDK sends the error to on_error and exports the span.

Automatic metrics

The SDK records gen_ai.client.operation.duration for generation, agent, embedding, and tool spans. It records gen_ai.client.token.usage for generation, agent, and embedding spans.

Tool spans do not add data to the token histogram. Plain function spans do not add data to either histogram.

The metrics use DELTA temporality. Metrics record only for spans that pass span_filter.

Flush and shutdown

flush(timeout_s=10.0) sends pending traces, logs, and metrics. shutdown(timeout_s=10.0) flushes, releases resources, and resets the active client.

The SDK registers an automatic atexit shutdown hook by default. Set disable_atexit=True to prevent this registration.

For long-running servers, use the default batched mode. The default processor values are:

Batch value Default
Queue size 2048
Delay 1000 milliseconds
Export batch size 64
Export timeout 30000 milliseconds

For short scripts, flush and shut down before exit:

telemetry_dev.flush()
telemetry_dev.shutdown()

For serverless functions, use immediate export and call flush() before the runtime freezes:

telemetry_dev.init(export_mode="immediate")

# Handle the request.
telemetry_dev.flush()

Immediate mode affects spans and logs. The metric reader keeps a 60-second interval, so serverless functions must call flush() to send metrics.

Provider integrations

Use a provider package to capture model requests, responses, tokens, stream data, and provider errors.

Last updated on August 3, 2026

Was this page helpful?