# 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.

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](https://belvador.dev/en/docs/otel/quickstart#2-set-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

```bash
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:

```bash
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
export OTEL_SEMCONV_STABILITY_OPT_IN=http
opentelemetry-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

```bash
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](https://belvador.dev/en/docs/otel/overview#one-tracing-owner), and add `OTLPIntegration` so errors carry the active span — set up on [With Sentry SDKs](https://belvador.dev/en/docs/otel/sentry-coexistence#python-sentry-sdk). Belvador has not yet run a Python app with both, so that link is not marked tested.
