Integrating OpenTelemetry with FastAPI for Production Observability
- Authors

- Name
- Nino
- Occupation
- Senior Tech Editor
Observability is no longer a luxury for modern web applications; it is a fundamental requirement. As systems move toward microservices and rely heavily on external providers—such as using n1n.ai for high-performance LLM API aggregation—understanding the flow of data and identifying bottlenecks becomes increasingly complex. In this tutorial, we will explore how to integrate OpenTelemetry (OTel) into a FastAPI application to provide deep insights into your system's performance.
Why OpenTelemetry for FastAPI?
FastAPI is renowned for its speed and asynchronous capabilities. However, when your app scales or integrates with multiple Large Language Models like DeepSeek-V3 or Claude 3.5 Sonnet, a simple log entry isn't enough to diagnose why a specific request took 5 seconds instead of 500ms. OpenTelemetry provides a vendor-neutral standard for collecting traces, metrics, and logs, allowing you to visualize the entire lifecycle of a request.
Step 1: Setting Up the Infrastructure with Jaeger
Before instrumenting the code, we need a backend to store and visualize our telemetry data. Jaeger is the industry standard for distributed tracing. For local development, the 'all-in-one' Docker image is the most efficient choice.
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
jaegertracing/all-in-one:latest
- Port 16686: The Jaeger UI (where you view traces).
- Port 4317: OTLP gRPC receiver.
- Port 4318: OTLP HTTP receiver.
Step 2: Installing Dependencies
We need the core OpenTelemetry SDK, the FastAPI instrumentation wrapper, and the OTLP exporter to push data to Jaeger. If you are building an AI agent that calls n1n.ai, you should also install instrumentation for your HTTP client (like httpx).
pip install fastapi uvicorn \
opentelemetry-api \
opentelemetry-sdk \
opentelemetry-instrumentation-fastapi \
opentelemetry-exporter-otlp
Step 3: Implementing Automatic Instrumentation
OpenTelemetry offers 'Automatic Instrumentation' which requires zero code changes to the business logic. We configure the tracer provider and attach the FastAPI instrumentor.
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.sdk.resources import RESOURCE_ATTRIBUTES, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
# 1. Setup Resource (Service Metadata)
resource = Resource.create(attributes={
"service.name": "fastapi-llm-service",
"service.version": "1.0.0"
})
# 2. Setup Tracer Provider
tracer_provider = TracerProvider(resource=resource)
# 3. Setup Exporter (Sending data to Jaeger)
otlp_exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)
span_processor = BatchSpanProcessor(otlp_exporter)
tracer_provider.add_span_processor(span_processor)
# 4. Set Global Tracer
trace.set_tracer_provider(tracer_provider)
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello Observability"}
# 5. Instrument FastAPI
FastAPIInstrumentor.instrument_app(app)
Step 4: Monitoring LLM API Latency (Pro Tip)
When your FastAPI app acts as a gateway to LLMs via n1n.ai, you want to know exactly how long the model inference takes versus your internal processing. You can create manual spans to wrap these calls.
import httpx
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
@app.post("/ask-ai")
async def ask_ai(prompt: str):
with tracer.start_as_current_span("n1n-api-call") as span:
span.set_attribute("model", "deepseek-v3")
async with httpx.AsyncClient() as client:
# Example calling n1n.ai
response = await client.post(
"https://api.n1n.ai/v1/chat/completions",
json={"model": "deepseek-v3", "messages": [{"role": "user", "content": prompt}]},
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
return response.json()
Comparison: Auto vs. Manual Instrumentation
| Feature | Automatic Instrumentation | Manual Instrumentation |
|---|---|---|
| Setup Effort | Minimal (few lines) | Significant (per function) |
| Granularity | Request/Response level | Internal logic/Sub-functions |
| Context | Generic HTTP metadata | Custom business attributes |
| Maintenance | Easy | High |
Advanced: Log Correlation
Tracing tells you where the latency is, but logs tell you why. By injecting the trace_id and span_id into your application logs, you can jump from a trace in Jaeger directly to the corresponding log line in your logging system.
Modern OpenTelemetry Python SDKs can handle this via the LoggingInstrumentor. Ensure your log format includes %(otelTraceID)s and %(otelSpanID)s to make this correlation work seamlessly.
Performance and Sampling
In a high-traffic production environment, exporting 100% of traces can introduce overhead and inflate storage costs.
Pro Tip: Use a ParentBased(root=TraceIdRatioBased(0.1)) sampler. This ensures that only 10% of new requests are traced, but if a request starts a trace, all downstream spans are captured to maintain consistency.
Conclusion
Integrating OpenTelemetry with FastAPI transforms your application from a black box into a transparent system. Whether you are debugging a complex RAG pipeline or optimizing calls to n1n.ai, distributed tracing provides the clarity needed for production stability. By following these steps, you ensure that as your AI capabilities grow, your ability to manage them grows as well.
Get a free API key at n1n.ai