Skip to content

Tracing (OpenTelemetry)

pip install "endocore[otel]"

Instrument every request

# Middleware/__init__.py
from endocore.middleware import tracing_middleware

middlewares = [tracing_middleware()]

This opens one span per request (named "GET /v1/user/42"), tagged with http.method, http.target, http.status_code. A raised HTTPError sets http.status_code from its status but only marks the span as an error for 5xx — a NotFound isn't a server failure and shouldn't render as one in your trace backend. Any other unhandled exception is recorded on the span and marks it as an error, matching what logging_middleware already logs.

With no exporter configured, spans are still created — useful on its own for the trace-id/log correlation below — but never shipped anywhere.

Ship spans somewhere

# extensions.py
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from endocore.extensions.tracing import TracingExtension

extensions = [TracingExtension(exporter=OTLPSpanExporter(), service_name="my-app")]

EndoCore doesn't depend on any specific exporter package — install whichever one your collector needs (opentelemetry-exporter-otlp, a console exporter for local debugging, etc.) and pass the built instance in. TracingExtension sets it as the global TracerProvider and flushes pending spans on shutdown.

Correlating traces with logs

Every request already gets an X-Request-ID (see Logging). tracing_middleware additionally stashes the span's trace id on request.scope["trace_id"], so a handler (or another middleware) can log both side by side:

async def handler(request):
    log.info("trace=%s request=%s", request.scope.get("trace_id"), request.scope.get("request_id"))