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.