TracekitTracekit

FastAPI Monitoring with OpenTelemetry in Production

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

Terry Osayawe5 min read
FastAPI Monitoring with OpenTelemetry in Production

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

QuestionMeasureFirst check when it changes
Are users getting a response?Request count and server error count by routeOne failing route, release, or dependency
Are responses fast enough?Request duration distribution by routeSlow database, outbound HTTP call, or local work
Which operation caused the delay?A connected request traceThe 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:

  1. Find the /orders request span in the trace view.
  2. Confirm that it has the expected route and status attributes.
  3. Confirm that the call counter increases by one for each request.
  4. Confirm that the server-error counter increases only for the failure.
  5. Confirm that the duration histogram records values in milliseconds.
  6. 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 seeFirst action
One route's p95 rises after a releaseCompare the same route before and after that release.
Server errors rise with stable trafficOpen one error trace and check the failing span.
Request duration rises, but server work looks shortCheck queue time, network path, and client timing outside the server span.
One trace has repeated database spansTest for an N+1 query pattern. Use the N+1 guide.
A task fails after the HTTP responseCheck 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.

Share this post

Related Posts