‹ Back to Blog

FastAPI 0.142 Turns On OpenTelemetry by Default. Here's What It Records.

Engineering Python

FastAPI 0.142.0 ships with OpenTelemetry built in. Install fastapi[standard], set one environment variable, and every request produces a trace, metrics, and error logs. No middleware, no instrumentation package, no code.

That’s a big change for a framework most Python teams instrument by hand. It’s also easy to misread. The defaults record more than some teams expect in one place, and less than others assume in another. Here’s what you actually get.

Turning It On

The packages for sending telemetry come with the standard extras:

uv add "fastapi[standard]"

Then point FastAPI at any backend that accepts OTLP, the OpenTelemetry protocol:

export OTEL_SERVICE_NAME=my-api
export OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.example.com

Traces go to /v1/traces, metrics to /v1/metrics, and logs to /v1/logs under that URL. If your backend needs a key, set OTEL_EXPORTER_OTLP_HEADERS (for example api-key=YOUR_API_KEY).

What FastAPI Records by Default

Traces. Each HTTP request gets a span named after the route, like GET /items/{item_id}. Inside it, FastAPI adds spans for resolving dependencies, running your path operation function, serializing the response, and each task in BackgroundTasks.

Background task spans stay part of the request’s trace but run after the HTTP response span ends, so they don’t inflate your measured response time. WebSocket connections get their own span, such as WS /ws/{room}.

Metrics. Request counts, response duration, and active requests, for HTTP requests only.

Logs. Unhandled exceptions are recorded as OpenTelemetry logs linked to the request’s trace, including the exception type, message, and stack trace. They’re recorded even when the trace itself isn’t sampled. Request validation failures are logged as warnings with the route and error count, without the invalid input.

What It Doesn’t Record

FastAPI’s spans cover FastAPI’s own work. The docs list dependency resolution, your endpoint function, serialization, and background tasks. They don’t describe spans for what happens inside your endpoint: SQL queries, calls to other services, cache lookups.

So a slow request shows up as a slow path operation span, but not why it’s slow. If the time is in your database, you still need instrumentation for your database driver or ORM, from OpenTelemetry’s contrib packages or from an APM agent, to see the queries. The same goes for outgoing HTTP calls.

Four Settings to Check Before You Ship

All of these live in a telemetry dictionary on the app:

Setting What it does Default
tracing Request and WebSocket spans True
metrics HTTP request metrics True
logs Validation failures and unhandled exceptions True
operation_spans Spans for dependencies, endpoint, serialization, background tasks True
exclude Function that skips requests based on the ASGI scope None
auto_configure Add exporters for endpoints set in environment variables True

1. Don’t send everything twice

If you already use OpenTelemetry’s FastAPI instrumentation, Logfire, or another library that configures exporters from the same environment variables, FastAPI adds its own exporter for that destination too. The docs say to configure each destination once: either turn off the other library’s environment export or turn off FastAPI’s setup:

app = FastAPI(telemetry={"auto_configure": False})

Check this first on upgrade. Duplicate exports mean duplicate data and, on usage-priced backends, a bigger bill.

2. Decide what exception logs may contain

Exception logs include messages and stack traces, which can contain sensitive values. Filter or redact them with your provider’s log processors, or turn them off with "logs": False if your error tracker already captures exceptions.

3. Exclude health checks

Load balancer health checks can be most of your traffic and none of your signal. Skip them:

app = FastAPI(
    telemetry={
        "exclude": lambda scope: scope["path"] == "/health",
    }
)

4. Trim spans if you only want the request

If you only want one span per request, set "operation_spans": False. You lose the breakdown between dependencies, your code, and serialization, which is often the useful part, so leave it on unless volume is a problem.

One limitation to know: OpenTelemetry uses global providers, so independent telemetry settings for mounted sub-applications aren’t guaranteed.

Where This Fits

Native telemetry is a good default. Every FastAPI app now produces standard request traces and metrics with no setup, and tools no longer need to patch FastAPI internals to get them. The PR notes it was designed with OpenTelemetry’s contrib instrumentation, Logfire, and Sentry in mind.

It’s the same direction Starlette took in 1.7.0, which added native tracing and exposed the matched route. Frameworks are taking responsibility for describing their own requests.

What’s left to you is the part the framework can’t see: what your code does with the database and other services, and whether it’s getting worse over time. Scout Monitoring covers that for FastAPI apps. The Python agent captures SQLAlchemy queries and flags N+1 patterns automatically, tracks external HTTP calls and background tasks, and links errors to the request trace that caused them.

Try Scout Monitoring free. No credit card needed.