Auto-Instrumentation for FastAPI

Zero-code tracing for HTTP requests, database calls, and external API calls with OpenTelemetry

10m 10m reading Lab included

The Problem

Manually wrapping every function in spans is tedious and error-prone. You’d need to modify every route handler, database call, and HTTP client. Auto-instrumentation does this for you.

What Auto-Instrumentation Traces

FastAPI Instrumentation

FastAPIInstrumentor.instrument_app(app)

This single line traces:

  • Every incoming HTTP request (method, path, status code, duration)
  • Route matching
  • Exception propagation

Each request becomes a root span with attributes:

Attribute Example
http.method POST
http.target /api/v1/items/
http.status_code 201
http.route /api/v1/items/

HTTP Client Instrumentation

When your app calls external services using httpx:

pip install opentelemetry-instrumentation-httpx
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
HTTPXClientInstrumentor().instrument()

Now every httpx.get() or httpx.post() creates a child span with the outgoing request details and propagates the traceparent header.

Database Instrumentation

pip install opentelemetry-instrumentation-sqlalchemy
# or
pip install opentelemetry-instrumentation-pymongo
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
SQLAlchemyInstrumentor().instrument(engine=engine)

Every SQL query becomes a span with the query text and duration.

What a Trace Looks Like

Trace: 4bf92f3577b34da6a3ce929d0e0e4736
│
├─ POST /api/v1/items/ [45ms]
│  ├─ validate_request [2ms]
│  ├─ INSERT INTO items... [28ms]  ← DB instrumentation
│  ├─ POST https://webhook.site/notify [12ms]  ← HTTP client
│  └─ serialize_response [1ms]

In Jaeger UI, this renders as a waterfall diagram — immediately showing that the DB insert takes 62% of total time.

Logging Integration

LoggingInstrumentor().instrument(set_logging_format=True)

After this, every log record includes otelTraceID and otelSpanID:

{
  "event": "item_created",
  "request_id": "abc-123",
  "otelTraceID": "4bf92f3577b34da6a3ce929d0e0e4736",
  "otelSpanID": "00f067aa0ba902b7"
}

Click the trace ID in Kibana → opens the trace in Jaeger. Logs and traces are linked.

Dependencies

# pyproject.toml
dependencies = [
    "opentelemetry-api>=1.29.0",
    "opentelemetry-sdk>=1.29.0",
    "opentelemetry-instrumentation-fastapi>=0.50b0",
    "opentelemetry-instrumentation-logging>=0.50b0",
    "opentelemetry-exporter-otlp>=1.29.0",
]

Zero-Code Alternative

OpenTelemetry also supports fully automatic instrumentation via the opentelemetry-instrument wrapper:

opentelemetry-instrument uvicorn app.main:app

This instruments everything without any code changes — but gives less control. Our approach (programmatic instrumentation) lets us control exactly what’s traced and configure resources.

Next Step

In the next lesson, we deploy Jaeger and visualize our traces — finding latency bottlenecks in real time.