FastAPI Monitoring with OpenTelemetry in Production
Monitor FastAPI routes with request metrics and OpenTelemetry traces. Check latency, errors, and downstream work before setting alerts.

FastAPI monitoring with OpenTelemetry needs two views. Request metrics show which route is slow or failing across many calls. A trace shows why one request was slow. If you collect only traces, sampled requests can leave gaps in your rates. If you collect only metrics, you can see a problem without its database or HTTP call path.
This guide gives you a small production setup, a test plan, and a way to turn the data into useful alerts. The examples use current OpenTelemetry FastAPI instrumentation and the Tracekit Python SDK. You can use the same monitoring plan with another OpenTelemetry backend.
Start with three route-level questions
| Question | Measure | First check when it changes |
|---|---|---|
| Are users getting a response? | Request count and server error count by route | One failing route, release, or dependency |
| Are responses fast enough? | Request duration distribution by route | Slow database, outbound HTTP call, or local work |
| Which operation caused the delay? | A connected request trace | The slow child span and its parent service |
Use route templates such as /orders/{order_id} for grouping. Raw paths such as /orders/123 create a separate group for each order. OpenTelemetry's HTTP metric conventions define http.server.request.duration and the low-cardinality http.route attribute. Check the names that your installed instrumentation actually emits. The conventions have changed across versions.
Decide how your service treats a bad request. A 404 for an unknown order may be expected. A 500 is usually a server failure. Do not combine all non-200 responses into one error rate without a service-specific rule. Also separate health checks and test traffic from the user request group when you set an alert.
Instrument FastAPI requests once
The official FastAPI instrumentor creates HTTP request spans. Tracekit's Python SDK configures a tracer provider and exporter. This example uses that provider with the official instrumentor:
import os
import tracekit
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
client = tracekit.init(
api_key=os.environ["TRACEKIT_API_KEY"],
service_name="orders-api",
)
app = FastAPI()
FastAPIInstrumentor.instrument_app(app, tracer_provider=client.provider)
@app.get("/orders")
async def list_orders():
return {"orders": ["A123", "B456"]}
Install tracekit-apm[fastapi] and opentelemetry-instrumentation-fastapi for this path. Keep the API key in the process environment. The FastAPI tracing guide shows how to add SQLAlchemy and HTTPX spans so the request trace includes downstream work.
Tracekit also provides init_fastapi_app() as another request middleware path. Choose one path for each app. Two request instrumentors can create duplicate spans. The current code exposes client.provider, and the official instrumentor accepts it as tracer_provider.
A request span alone does not prove you have complete request metrics. Confirm that your installed instrumentation exports the metrics you plan to use. The Tracekit Python SDK also offers counters and histograms that export OTLP metrics. You can use them for a defined route when you need a direct measurement.
Add a small metric check for one route
This example counts calls and server failures on /orders. It records duration in milliseconds. It uses the Tracekit SDK's current counter() and histogram() APIs:
from time import perf_counter
from fastapi import Request
orders_calls = client.counter("orders.calls")
orders_server_errors = client.counter("orders.server_errors")
orders_duration = client.histogram("orders.duration_ms", tags={"unit": "ms"})
@app.middleware("http")
async def measure_orders(request: Request, call_next):
if request.url.path != "/orders":
return await call_next(request)
started = perf_counter()
status_code = 500
try:
response = await call_next(request)
status_code = response.status_code
return response
finally:
orders_calls.inc()
if status_code >= 500:
orders_server_errors.inc()
orders_duration.record((perf_counter() - started) * 1000)
This is a narrow example for one fixed route. Use a route template and a bounded set of dimensions for a larger API. Do not make a metric name or tag from a user ID, raw URL, or request body. Keep this metric middleware separate from the request span instrumentation, and check that it does not count the same signal twice in your dashboard.
The example counts an unhandled exception as a server failure. It does not count an expected 404 as one. Change the rule only when your users and service contract require it.
Verify a successful and a failing request
Run one normal call and one deliberate server failure in a test environment. Then check the data, not just the HTTP response:
- Find the
/ordersrequest span in the trace view. - Confirm that it has the expected route and status attributes.
- Confirm that the call counter increases by one for each request.
- Confirm that the server-error counter increases only for the failure.
- Confirm that the duration histogram records values in milliseconds.
- If the route uses a database or HTTPX, confirm those spans sit under the same request.
If you see no server span, check SDK initialization, exporter response, and sampling. If you see the request span but no SQL span, instrument the actual database client. If a downstream span starts a new trace, check context propagation. The FastAPI tracing setup gives a step-by-step boundary test.
Do not assume every request trace reaches storage. A sampler can drop trace detail before export. Keep your request metrics independent of sampled trace counts when you calculate rates.
Turn measurements into a service objective
Define a good request before you set a threshold. For example, a checkout API may count a request as good when it returns without a server error and finishes within 500 ms. The success indicator is:
good requests / eligible requests
If 100,000 eligible requests arrive in one window, a 99.9% objective allows 100 bad requests. This is an example, not a recommended target. Google's SRE guidance recommends a user-centered indicator and an agreed objective. Choose your own window, traffic exclusions, and latency limit from real user needs.
Use the request metrics to watch the objective. Use a trace to investigate a bad request. The Tracekit alerting feature and alert rules can notify you about supported service conditions. Set each rule from your measured baseline. A trace-only alert can miss sampled-out requests, so review the trace alert and sampling guide before relying on one.
Triage the next slow route
| What you see | First action |
|---|---|
| One route's p95 rises after a release | Compare the same route before and after that release. |
| Server errors rise with stable traffic | Open one error trace and check the failing span. |
| Request duration rises, but server work looks short | Check queue time, network path, and client timing outside the server span. |
| One trace has repeated database spans | Test for an N+1 query pattern. Use the N+1 guide. |
| A task fails after the HTTP response | Check the FastAPI background task checklist. |
Start with one busy route. Prove that metrics show its user impact and a trace explains one case. Then apply the same test to the next route. This keeps FastAPI monitoring tied to evidence you can act on.
Related Posts

FastAPI Tracing with OpenTelemetry: A Practical Setup
Set up FastAPI tracing with OpenTelemetry. Connect request, SQLAlchemy, and HTTPX spans, then verify trace context across services.

How to Monitor Python Web Apps with OpenTelemetry
Monitor Python web applications with OpenTelemetry using auto-instrumentation, OTLP export, dependency spans, validation, and production safeguards.

FastAPI Background Tasks: Production Checklist
Use this FastAPI background tasks production checklist to catch silent failures, blocked workers, lost trace context, and release regressions.