mrkeyoor.com_
Thu 06 Aug 01:02 UTC
PyPIInfraupdated 05 Aug 2026

opentelemetry-api

opentelemetry-api is the vendor-neutral Python interface for emitting traces, metrics, and logs under the CNCF OpenTelemetry standard. On its own it does nothing: every call is a no-op until an application installs opentelemetry-sdk (or another implementation) and wires up exporters. That split is deliberate. Libraries depend only on this API so they can emit telemetry without dragging in a backend, and the app decides at deploy time whether spans go to Jaeger, Grafana, Datadog, or nowhere.

Verdict

The industry-standard telemetry API for Python and the only sane choice for library authors; traces and metrics are stable and every vendor accepts the output. Application teams should budget real time for the multi-package setup, and anyone who just needs error reporting will be happier with Sentry.

API stability4/5Traces and metrics are declared stable with versioning guarantees and the 1.x line goes back years; logs are still marked development, and the project itself warns of breaking changes while that signal stabilizes.
Docs3/5The opentelemetry.io getting-started guide is solid, but reference docs live on readthedocs, instrumentation docs live in the contrib repo, and figuring out which exporter/instrumentation package combination you need takes real digging.
Maintenance5/5CNCF governance, named maintainers from Google, Microsoft, and Elastic, weekly SIG meetings, and a push the day of this review; new Python versions are supported within three months by policy.
Ecosystem5/5OTLP is accepted by essentially every observability vendor, the contrib repo instruments most popular frameworks and clients, and the API package sits in the dependency tree of much of the modern Python service stack.

Use it if

  • You maintain a Python library and want to emit spans or metrics without forcing an SDK or vendor on your users; the API-only dependency is the officially sanctioned pattern
  • You want telemetry that survives a vendor switch: instrument once against OTel, then swap the exporter instead of rewriting instrumentation
  • Your backend already speaks OTLP (Grafana, Jaeger, Honeycomb, Datadog, and every major vendor now does), so the collector path is paved
  • You need traces and metrics correlated across services in different languages; W3C trace context propagation is the whole point
Skip it if

Setup reality

pip install opentelemetry-api opentelemetry-sdk is only the start. You still choose an exporter package (OTLP over gRPC and over HTTP are separate distributions), configure a TracerProvider with a BatchSpanProcessor, and set resource attributes like service.name or everything lands as unknown_service. Auto-instrumentation needs opentelemetry-distro plus opentelemetry-bootstrap to install per-library packages, all version-pinned to the api/sdk pair, and upgrades cascade through the whole set. Requires Python 3.10+ as of the current line.

Patterns

Get a tracer and open a spancreate-span

from opentelemetry import trace

tracer = trace.get_tracer("myapp.orders")

with tracer.start_as_current_span("process-order") as span:
    span.set_attribute("order.id", order_id)
    do_work()

Without an SDK TracerProvider configured, this whole block is a silent no-op: no error, no output. That is by design for libraries, and confusing the first time you test locally.

Wire the SDK to an OTLP endpoint (app startup)configure-sdk-otlp

from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

provider = TracerProvider(
    resource=Resource.create({"service.name": "orders-api"})
)
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

Needs opentelemetry-sdk and opentelemetry-exporter-otlp-proto-http installed; the gRPC exporter is a different package. set_tracer_provider only takes effect once per process, so do this before anything grabs a tracer.

Print spans to stdout while developingdebug-with-console-exporter

from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
    SimpleSpanProcessor,
    ConsoleSpanExporter,
)

provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))

SimpleSpanProcessor exports synchronously on span end, which is what you want when eyeballing output; switch back to BatchSpanProcessor for anything real or you pay the export cost inline.

Record an exception and mark the span failedrecord-exception

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

with tracer.start_as_current_span("charge-card") as span:
    try:
        charge(card)
    except PaymentError as exc:
        span.record_exception(exc)
        span.set_status(Status(StatusCode.ERROR, str(exc)))
        raise

start_as_current_span already records uncaught exceptions and sets error status by default; the explicit calls matter when you catch and handle, or when you used start_span directly.

Nest spans and reach the current one from anywherenested-spans

with tracer.start_as_current_span("http-request"):
    with tracer.start_as_current_span("db-query") as child:
        child.set_attribute("db.statement", "SELECT ...")

# deep in a helper, no span handle in sight:
from opentelemetry import trace
trace.get_current_span().set_attribute("cache.hit", True)

Parenting flows through contextvars automatically, including across await points; you never pass span objects around by hand.

Add timestamped events to a spanspan-events

with tracer.start_as_current_span("sync-job") as span:
    span.add_event("cache-miss", {"key": key})
    refresh(key)
    span.add_event("cache-refreshed", {"size": size})

Events are cheap point-in-time annotations inside one span; if the step has meaningful duration you want a child span instead so it shows up in the waterfall.

Create a counter and a histogramcount-metric

from opentelemetry import metrics

meter = metrics.get_meter("myapp.orders")
orders = meter.create_counter("orders.processed", unit="1")
latency = meter.create_histogram("orders.duration", unit="ms")

orders.add(1, {"region": "eu"})
latency.record(elapsed_ms, {"region": "eu"})

Same no-op rule as tracing: nothing is exported until the app sets a MeterProvider from the SDK. Keep attribute cardinality low; user ids as attributes will melt your metrics backend.

Propagate trace context across HTTP callspropagate-context-http

from opentelemetry import trace
from opentelemetry.propagate import inject, extract

# client side: stamp outgoing headers
headers = {}
inject(headers)
requests.get(url, headers=headers)

# server side: continue the caller's trace
ctx = extract(request.headers)
with tracer.start_as_current_span("handle", context=ctx):
    ...

inject/extract speak W3C traceparent by default. The framework instrumentation packages do this for you; hand-rolling it is only needed for custom transports like queues.

Carry app data across services with baggagebaggage-cross-service

from opentelemetry import baggage, context

token = context.attach(baggage.set_baggage("tenant.id", "acme"))
try:
    call_downstream()  # propagators forward the baggage header
finally:
    context.detach(token)

# downstream service:
tenant = baggage.get_baggage("tenant.id")

Baggage rides plaintext HTTP headers to every downstream hop, so never put secrets or PII in it. It is separate from span attributes; copy it onto spans yourself if you want it indexed.

Zero-code instrumentation via the agent CLIauto-instrument-app

pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install

OTEL_SERVICE_NAME=orders-api \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
opentelemetry-instrument python app.py

bootstrap scans installed packages and pip-installs matching instrumentation for Flask, Django, requests, SQLAlchemy, and friends. Fast to demo, but pin the resulting package set or the lockstep versioning will bite on the next deploy.

Alternatives

PackageRegistryPick it when
sentry-sdkPyPIError tracking and performance monitoring for one app with a single package and near-zero config.
ddtracePyPIYou are committed to Datadog and want their agent's auto-instrumentation instead of assembling OTel pieces.
logfirePyPIYou want OTel-based observability with a batteries-included developer experience from the Pydantic team.
elastic-apmPyPIYour observability stack is Elastic and you prefer their native agent over the OTLP route.