OpenTelemetry for FastAPI


What is FastAPI

FastAPI is a Python framework for building, you guessed it, APIs. I especially like it because it’s so simple to use.

What is OTel

OTel, or better OpenTelemetry, is the next iteration of defining one standard to ship traces, metrics and logs in a common format so you’ll never be vendor-locked for your observability solution.

Since all major observability tools are heavily investing on it and we want to move to a different solution in the future, this is the best solution to let your code run anywhere.

For many programming languages and frameworks there are packages available, including FastAPI: opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/fastapi

What am I missing?

So I added the code from the documentation above, but nothing arrived at my collector. What was missing?

Turns out, if you don’t use the complete auto-instrumentation, you have to add an exporter yourself! Check out a complete implementation below:

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

#create your FastAPI app
app = FastAPI()

trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)

otlp_exporter = OTLPSpanExporter()
span_processor = BatchSpanProcessor(otlp_exporter) # we don't want to export every single trace by itself but rather batch them
otlp_tracer = trace.get_tracer_provider().add_span_processor(span_processor)

# now the official documented part
FastAPIInstrumentor.instrument_app(app,tracer_provider=otlp_tracer)

@app.get("/")
async def index():
    return {"foo": "bar"}

Kubernetes Deployment

So now, after getting the stuff inside your container right, it’s time to spiff out the outer parts. Luckily, you basically just have to set your OTel Endpoint via OTEL_EXPORTER_OTLP_ENDPOINT.

Hint: It’s the same variable name, no matter which language you use, one of the elegancies of OTel…

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fastapi-otel-app
spec:
  selector:
    matchLabels:
      app: fastapi-otel-app
  template:
    metadata:
      labels:
        app: fastapi-otel-app
  spec:
    containers: 
    - image: fastapi-otel-app:latest
      command: ["/venv/bin/uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
      env:
      - name: OTEL_EXPORTER_OTLP_ENDPOINT
        value: "http://otel-collector.svc.monitoring.cluster.local:4317"

Wrap up

At the time writing this the documentation just tells you to add

FastAPIInstrumentor.instrument_app(app)

to your code and you’re done. That you have to add your exporter is no part of the documentation and I spend a good day until I found out about it through various blogposts and examples.

Hopefully I can improve the official documentation as well, so you don’t even have to read this blogpost ;-)