OpenTelemetry
FastAPI Views provides optional OpenTelemetry integration that automatically:
- Instruments your FastAPI application using
FastAPIInstrumentor. - Captures the active trace ID for each request and stores it in a context variable.
- Injects the trace ID as a
correlation_idfield in every error response.
This makes it trivial to correlate an error a user reports with a specific trace in your observability backend (Jaeger, Zipkin, Grafana Tempo, etc.).
Installation
Install the opentelemetry extra:
Or install the instrumentation package directly:
The integration is opt-in — if the package is not installed, everything works normally and error responses will not include a correlation_id field.
How it works
When configure_app(app) is called and opentelemetry-instrumentation-fastapi is available, FastAPI Views:
- Calls
FastAPIInstrumentor.instrument_app(app)with aserver_request_hookthat reads the current span's trace ID and stores it in aContextVar(CORRELATION_ID). - The
ErrorDetailsmodel conditionally declares acorrelation_idfield whose default factory reads from thatContextVar. - Every error response — whether from an
APIError, a validation error, or an unhandled exception — will automatically include thecorrelation_idof the trace that triggered it.
Setup
Configure a TracerProvider (pointing at your observability backend), then call configure_app. No other changes are required.
import logging
import socket
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleSpanProcessor,
)
from fastapi_views import configure_app
logging.basicConfig(level=logging.INFO)
resource = Resource(
attributes={
"service.name": "my-api",
"service.version": "1.0.0",
"service.instance.id": socket.gethostname(),
}
)
provider = TracerProvider(resource=resource)
trace.set_tracer_provider(provider)
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
# Propagate trace IDs into log records
LoggingInstrumentor().instrument()
app = FastAPI(title="My API")
configure_app(app)
@app.get("/test")
async def raise_error():
# Any unhandled exception is caught by configure_app's error handlers.
# The response will include the correlation_id of the current trace.
raise ValueError("Something went wrong")
Error response with correlation_id
When an error occurs during a traced request, the response body will include the trace ID:
{
"type": "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1",
"title": "Internal Server Error",
"status": 500,
"detail": "Internal server error",
"instance": "/test",
"correlation_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": []
}
The correlation_id value is the W3C trace ID formatted as a 32-character hex string. Pass this ID to your tracing UI to find the full span and all associated logs.
Passing options to FastAPIInstrumentor
Any keyword arguments passed to configure_app beyond its own parameters are forwarded to FastAPIInstrumentor.instrument_app. This lets you configure excluded URLs, custom span name formatting, etc.:
configure_app(
app,
excluded_urls="/healthz,/metrics",
span_name_formatter=lambda scope: scope["path"],
)
See the OpenTelemetry FastAPI Instrumentation docs for the full list of available options.
Sending traces to a real backend
Replace ConsoleSpanExporter with the exporter for your backend.
OTLP via gRPC:
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4317"))
)
OTLP via HTTP:
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces")
)
)
Complete example
import logging
import socket
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleSpanProcessor,
)
from fastapi_views import configure_app
logging.basicConfig(level=logging.INFO)
resource = Resource(
attributes={
"service.name": "test-api",
"service.version": "0.1.0",
"service.instance.id": socket.gethostname(),
},
)
provider = TracerProvider(resource=resource)
trace.set_tracer_provider(provider)
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
LoggingInstrumentor().instrument()
app = FastAPI(title="My API")
configure_app(app)
@app.get("/test")
async def raise_error():
# example of Internal Server Error being returned, with exception being recorded and correlation id returned
raise ValueError("Server side error")