Why use OpenTelemetry for Python
OpenTelemetry is the open standard for application telemetry. For Python services it gives you one SDK that emits logs, distributed traces, and metrics in a single wire format — OTLP — that any compatible backend can read. That means you can instrument once and switch backends, run multiple backends in parallel, or move from local Jaeger to a hosted platform like Telemtra without rewriting application code.
For Python specifically, OpenTelemetry replaces a patchwork of older tools: logging handlers that ship to one vendor, APM agents that wrap WSGI middleware, and custom statsd emitters. The OTel Python SDK covers all three signal types and ships auto-instrumentation for most popular libraries — requests, urllib3, psycopg2, SQLAlchemy, Redis, Kafka, boto3, Flask, Django, FastAPI, Celery, asyncio — so you usually do not have to touch business code to get a working trace.
The rest of this page assumes Python 3.9 or newer and a Telemtra workspace API key. If you do not have a workspace yet, start free and copy the API key from the workspace settings page.
Install the OpenTelemetry Python SDK
OpenTelemetry for Python is split into small packages so you can install only what you need. The minimum for sending OTLP to Telemtra is the API, SDK, and OTLP HTTP exporter:
pip install \
opentelemetry-api \
opentelemetry-sdk \
opentelemetry-exporter-otlp-proto-http For auto-instrumentation, also install the bootstrap and contrib packages:
pip install opentelemetry-distro opentelemetry-instrumentation
opentelemetry-bootstrap --action=install opentelemetry-bootstrap inspects your installed packages and pulls in matching instrumentations automatically — for example, if Flask and SQLAlchemy are present, it installs opentelemetry-instrumentation-flask and opentelemetry-instrumentation-sqlalchemy.
Where this goes in your project
You do not need a separate "telemetry module" for the auto-instrumentation path — every change is environment variables and the launcher. A typical Python project looks like this after wiring up Telemtra:
my-service/
├── app.py # your existing Flask/Django/FastAPI app (unchanged)
├── requirements.txt # add the OTel packages below
├── .env # local OTEL_* and TELEMTRA_API_KEY
└── Dockerfile # CMD becomes "opentelemetry-instrument python app.py" Three concrete changes:
1. Add to requirements.txt:
opentelemetry-distro
opentelemetry-exporter-otlp-proto-http
opentelemetry-instrumentation 2. Add a .env (or set in your hosting provider's env config):
TELEMTRA_API_KEY=tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OTEL_SERVICE_NAME=checkout-api
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.telemtra.io
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS=x-api-key=${TELEMTRA_API_KEY} 3. Change the launch command. Wherever you currently run python app.py, gunicorn app:app, or uvicorn app:app, wrap it with the OTel launcher:
opentelemetry-instrument python app.py
opentelemetry-instrument gunicorn -w 4 app:app
opentelemetry-instrument uvicorn app:app --host 0.0.0.0 --port 8000 That is it. No imports added to your code, no startup hooks, no patching at the top of app.py. The launcher loads OpenTelemetry before your app code so auto-instrumentation can patch frameworks at import time.
If you do want manual spans or custom logs, those live in your normal application files — see the next sections.
Auto-instrumentation in 60 seconds
Auto-instrumentation wraps common Python libraries with OTel hooks at import time. You do not edit any source code; you run your app under the opentelemetry-instrument launcher with a few environment variables:
export OTEL_SERVICE_NAME=checkout-api
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production"
export OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.telemtra.io
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=$TELEMTRA_API_KEY"
opentelemetry-instrument python app.py That is the entire setup. OTEL_SERVICE_NAME becomes the service entry in Telemtra's catalog and Service Map. deployment.environment separates production from staging so dashboards and saved views can scope to the right slice. The OTLP HTTP exporter pushes batched spans, log records, and metric points to https://ingest.telemtra.io every second.
Manual spans for business logic
Auto-instrumentation covers framework boundaries — incoming HTTP requests, outgoing HTTP calls, DB queries, queue publishes. For business logic worth tracing on its own (a checkout, a fraud check, a slow report), add manual spans:
from opentelemetry import trace
tracer = trace.get_tracer("checkout-api")
def submit_order(order_id: str, total_cents: int):
with tracer.start_as_current_span("checkout.submit_order") as span:
span.set_attribute("order.id", order_id)
span.set_attribute("order.total_cents", total_cents)
charge_card(order_id, total_cents)
write_ledger(order_id)
send_receipt(order_id) The child function calls (charge_card, write_ledger, send_receipt) will automatically nest under checkout.submit_order as long as the outbound HTTP, SQL, or queue clients they use are auto-instrumented. The result in Telemtra is a single waterfall that shows where the slow link is — a real production debugging tool, not a synthetic metric.
Send Python logs as OTLP
OpenTelemetry's logging bridge lets you keep using Python's standard logging module while exporting log records as OTLP. The advantage is correlation: every log emitted inside an active span is automatically tagged with the trace ID and span ID, so you can jump from a slow trace in the Service Map to the exact log lines it produced.
import logging
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
logger_provider = LoggerProvider()
set_logger_provider(logger_provider)
logger_provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter())
)
handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
logging.info("order accepted", extra={"order_id": "ord_12345", "user_id": 882}) The extra dict becomes structured fields in Telemtra — searchable, filterable, and usable for Log Analyze and grouped-field alerts.
Metrics: counters, gauges, histograms
The OpenTelemetry metrics API in Python supports counters, up/down counters, gauges (observable), and histograms (explicit and exponential bucket). Telemtra ingests all of them through the same OTLP endpoint.
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
reader = PeriodicExportingMetricReader(OTLPMetricExporter(), export_interval_millis=10_000)
metrics.set_meter_provider(MeterProvider(metric_readers=[reader]))
meter = metrics.get_meter("checkout-api")
orders_total = meter.create_counter(
"orders_total",
description="Number of orders submitted",
)
checkout_latency = meter.create_histogram(
"checkout_latency_ms",
description="Checkout end-to-end latency",
unit="ms",
)
orders_total.add(1, {"plan": "team", "region": "eu-west-1"})
checkout_latency.record(348, {"region": "eu-west-1"}) Once these flow into Telemtra you can chart them on dashboards with count, sum, average, p50, p90, p95, and p99 modes, and define threshold alert rules — for example, page when checkout_latency_ms p95 exceeds 1500 ms for five minutes.
Pointing the OTLP exporter at Telemtra
Telemtra accepts both OTLP transports. Pick HTTP/protobuf for simple deployments behind proxies, and gRPC for high-throughput environments with persistent connections to a Collector. The full set of environment variables most teams need:
# HTTP/protobuf (recommended for app-direct export)
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.telemtra.io
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS=x-api-key=YOUR_TELEMTRA_API_KEY
# OR gRPC (recommended via a Collector)
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.telemtra.io:443
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_EXPORTER_OTLP_HEADERS=x-api-key=YOUR_TELEMTRA_API_KEY If you want to keep app processes lean and centralize batching, run the OpenTelemetry Collector as a sidecar or daemon and point Python at http://localhost:4318 instead. The Collector forwards to Telemtra and handles retries, compression, and backoff.
Flask, Django, and FastAPI specifics
Flask
from flask import Flask
from opentelemetry.instrumentation.flask import FlaskInstrumentor
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app) Every route becomes a span; URL template, status code, and exception are recorded automatically.
Django
Add the instrumentor in manage.py or your WSGI entry before settings load:
from opentelemetry.instrumentation.django import DjangoInstrumentor
DjangoInstrumentor().instrument() Combine with opentelemetry-instrumentation-psycopg2 and opentelemetry-instrumentation-requests to capture DB queries and outbound HTTP.
FastAPI
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
app = FastAPI()
FastAPIInstrumentor.instrument_app(app) FastAPI's async stack is fully supported; spans propagate correctly across await boundaries.
Async, Celery, and background workers
Context propagation is the part most teams get wrong. In async Python, the active span is tracked via contextvars, which is preserved across await but not across asyncio.create_task unless you copy context explicitly. For most apps this works out of the box; for fan-out workloads, use contextvars.copy_context() to keep traces connected.
For Celery, install opentelemetry-instrumentation-celery. Trace context is injected into task headers, so a Celery task triggered from a Flask request shows up as a child of the request span in Telemtra's waterfall.
Resource attributes that matter in Telemtra
Telemtra uses resource attributes to populate the service catalog, separate environments, and group hosts. Set these at minimum:
service.name— stable identifier (no random suffixes).deployment.environment—production,staging,preview, etc.host.name— defaults from the OS but worth overriding in containers.
All three are passed via OTEL_RESOURCE_ATTRIBUTES as comma-separated key=value pairs.
Troubleshooting OTLP from Python
- No data appears in Telemtra. Check the API key header is named
x-api-key, notauthorization. The OTLP HTTP exporter base endpoint ishttps://ingest.telemtra.io— the SDK appends/v1/traces,/v1/metrics, and/v1/logsfor you. - 403 or 401 from the exporter. The key is wrong, revoked, or scoped to a different workspace. Rotate it from workspace settings.
- Traces appear but no logs. The Python logging bridge requires an explicit handler — see the logs section. Auto-instrumentation does not enable it by default.
- Spans look disconnected. You are likely starting work in a new thread or task without copying context. Use
contextvars.copy_context()or instrument the worker library. - Quota warnings at 80%. Telemtra warns at 80% and 90% of your monthly ingest. Either upgrade the plan or apply Data Shaper rules to drop noisy logs before they count.
OpenTelemetry Python FAQ
Does OpenTelemetry Python work with Flask, Django, and FastAPI?
Yes. The opentelemetry-bootstrap script detects Flask, Django, and FastAPI in your environment and installs the matching auto-instrumentations (opentelemetry-instrumentation-flask, opentelemetry-instrumentation-django, opentelemetry-instrumentation-fastapi). Launch your app under opentelemetry-instrument and incoming HTTP, database, and outbound HTTP calls become spans without code changes.
Do I need to change my Python application source code?
No. The auto-instrumentation path (opentelemetry-instrument python app.py) patches frameworks at import time. You only add code if you want manual spans, custom metrics, or to configure a non-default sampler.
Can I send Python logs as well as traces?
Yes. Add the LoggingInstrumentor or wire the OpenTelemetry logging handler to the standard library's logging module. Each logger.info call becomes an OTLP log record with active trace_id and span_id attached so logs link back to their trace in Telemtra.
Does OpenTelemetry Python work with async, Celery, and asyncio?
Yes. asyncio and Celery have first-class auto-instrumentations. For custom worker libraries, propagate the active context with contextvars.copy_context() when starting tasks so child spans link to their parent.
What OTLP transport should I use - HTTP or gRPC?
Use HTTP/protobuf as the default - it works through HTTP proxies and most network policies. Switch to gRPC for high-throughput services where the persistent connection is worth the extra dependency. Both ship the same data to Telemtra.
Further reading
Next steps on Telemtra:
- Telemtra quickstart — minimum config to send your first OpenTelemetry signal, with copy-paste snippets for every supported stack.
- What is OTLP? — protocol details for what is on the wire.
- OpenTelemetry Collector quickstart — sidecar that buffers and batches before export.
- Data Shaper — drop or mask logs at ingest.
- Start a free Telemtra workspace to receive your first span.
Official OpenTelemetry references: