Skip to content

OpenTelemetry for Python

opentelemetry-instrument for Flask, Django, FastAPI and requests; turn on the logging handler so logs carry traces; running under Gunicorn or uWSGI.

Markdown
On this page

Python needs no code changes: opentelemetry-instrument starts the SDK before your app, instruments the libraries it finds (Flask, Django, FastAPI, requests, httpx, SQLAlchemy, psycopg, …) and reads the standard variables.

Tested on 2026-10-25 with python 3.12.15, opentelemetry-distro 0.66b1, opentelemetry-sdk 1.45.1, flask 3.1.3: traces passed, logs passed, logs carry the request's trace and span.

Install#

Terminal
pip install opentelemetry-distro opentelemetry-exporter-otlp-proto-http opentelemetry-instrumentation-logging# instrumentation for the libraries you already use (Flask, Django, requests, …)opentelemetry-bootstrap -a install

opentelemetry-bootstrap -a install looks at the packages already installed and adds the matching instrumentation packages. To review them first, run opentelemetry-bootstrap -a requirements and add the lines you want to your requirements file, pinned like the rest.

Run#

Put opentelemetry-instrument in front of the command that starts your app:

Terminal
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=trueexport OTEL_SEMCONV_STABILITY_OPT_IN=httpopentelemetry-instrument python app.py

The same works for servers: opentelemetry-instrument uvicorn main:app, opentelemetry-instrument flask run, or opentelemetry-instrument python manage.py runserver --noreload for Django (Django also needs DJANGO_SETTINGS_MODULE set, as usual).

Logs#

OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true adds a handler to the root logger, so every record from the standard logging module (and libraries built on it, such as structlog with the stdlib backend) is also sent as an OpenTelemetry log record with the active trace id and span id. Your existing handlers keep working. Records below the root logger's level are not sent; set the level you want with logging.basicConfig(level=logging.INFO) or your own config.

What the variables above set#

OTEL_SEMCONV_STABILITY_OPT_IN=http makes HTTP spans use the stable attribute names (url.path, http.request.method, http.response.status_code), which Belvador's filters use.

Forking servers (Gunicorn, uWSGI)#

Workers that are forked from a parent that already started OpenTelemetry lose the exporter's background thread, so they send nothing. With Gunicorn, do not use --preload together with opentelemetry-instrument, or start the SDK yourself in the post_fork hook; uWSGI needs lazy-apps = true. Belvador's run uses one Flask process, so forking setups are not part of what is marked tested.

Turning instrumentations off#

Terminal
export OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=urllib3,sqlite3

With Sentry's Python SDK#

Keep sentry-sdk for errors with tracing left off (no traces_sample_rate), so OpenTelemetry is the only tracing owner, and add OTLPIntegration so errors carry the active span — set up on With Sentry SDKs. Belvador has not yet run a Python app with both, so that link is not marked tested.

Posts on this topic

All posts

Spotted something wrong or missing? Tell us: hello@belvador.dev