hands-on

Instrument a FastAPI service with OpenTelemetry in 15 minutes.

Wire the OTel SDK, point it at the collector, and verify your first distributed trace — copy/paste guided, end to end.

Bruno Pires·May 09, 2026·1 min read

This is a short, copy/paste walk-through. By the end you'll have a FastAPI service producing real OpenTelemetry spans, exporting them to a collector, and verifying the first trace in Datadog (or any OTLP-compatible backend).

Prereqs

  • Python 3.11+
  • An OTLP-capable backend reachable from your laptop (we'll use a local otel/opentelemetry-collector container).

1. Install the SDK

pip install \
  opentelemetry-api \
  opentelemetry-sdk \
  opentelemetry-exporter-otlp \
  opentelemetry-instrumentation-fastapi

2. Bootstrap the tracer

# app/observability.py
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
    OTLPSpanExporter,
)

def setup_tracing(service_name: str) -> None:
    resource = Resource.create({"service.name": service_name})
    provider = TracerProvider(resource=resource)
    provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
    trace.set_tracer_provider(provider)

3. Wire it into FastAPI

# app/main.py
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from app.observability import setup_tracing

setup_tracing("orders-api")

app = FastAPI()
FastAPIInstrumentor.instrument_app(app)

@app.post("/orders")
async def create_order():
    return {"ok": True}

That's it. Every request to /orders now produces a span with HTTP attributes (http.method, http.route, http.status_code) and the FastAPI route handler.

4. Run the collector locally

docker run --rm -p 4317:4317 \
  -v $(pwd)/otelcol.yaml:/etc/otelcol/config.yaml \
  otel/opentelemetry-collector:latest

A minimal otelcol.yaml that prints spans to stdout:

receivers:
  otlp:
    protocols:
      grpc:

exporters:
  logging:
    loglevel: debug

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [logging]

5. Send a request, watch the span

curl -X POST http://localhost:8000/orders

Within a couple of seconds the collector should log a span named POST /orders. You just shipped your first trace.

Next steps

  • Add custom spans inside your handler with tracer.start_as_current_span().
  • Plug a real backend (Datadog, Honeycomb, Tempo) into the collector exporters section.
  • Run Horion against the service to see your traces score.