Distribueret tracing med OpenTelemetry
Instrumentér automatisk FastAPI, og viderefør trace-kontekst gennem downstream-HTTP- og databasekald.
Distribueret tracing med OpenTelemetry er en gratis Bootcamp i FastAPI-backendudvikling-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Bootcamp i FastAPI-backendudvikling, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Bootcamp i FastAPI-backendudvikling-kurset indeholder 4 lektioner i alt.
Hvorfor distribueret sporing?
I en mikrotjeneste eller selv i en backend med én tjeneste, der kommunikerer med andre HTTP-API'er og en database, bliver én brugeranmodning spredt ud over mange operationer. Når noget er langsomt eller fejler, kan logge alene ikke vise dig den årsagskæde på tværs af procesgrænser.
Distribueret sporing løser dette ved at give hver anmodning et fælles trace_id og opdele arbejdet i indlejrede spans:
- En trace = hele forløbet for én anmodning.
- En span = én tidsmålt arbejdsenhed (en HTTP-handler, en databaseforespørgsel, et udgående kald).
- Spans indeholder en
parent_span_id, som danner et træ.
OpenTelemetry (OTel) er den leverandørneutrale standard og SDK, vi bruger til at oprette disse traces fra FastAPI og sende dem til en backend som Jaeger, Tempo eller en OTLP-collector.
OpenTelemetrys datamodel
Før du forbinder noget, skal du forstå de centrale objekter, som du konfigurerer i koden:
- TracerProvider — fabrikken, der opretter tracere; du konfigurerer den én gang ved opstart.
- Tracer — hentes fra provideren og bruges til at starte spans.
- Span — har et navn, et start- og sluttidspunkt,
attributes(nøgle/værdi-tags),eventsog enstatus. - SpanProcessor — samler afsluttede spans i grupper (brug
BatchSpanProcessori produktion). - Exporter — serialiserer spans og sender dem ud (OTLP over gRPC/HTTP).
- Context — den tråd- eller opgavelokale beholder, der indeholder den aktuelt aktive span.
Forløbet er: TracerProvider → Tracer → Span → SpanProcessor → Exporter → backend.
Installation og grundopsætning af SDK'et
Til en FastAPI-backend installerer du SDK'et, OTLP-exporteren og instrumenteringspakkerne:
opentelemetry-sdk,opentelemetry-apiopentelemetry-exporter-otlpopentelemetry-instrumentation-fastapi,-httpx,-sqlalchemy
Ved opstart opretter du en TracerProvider med en Resource, der navngiver din tjeneste, tilknytter en BatchSpanProcessor, som omslutter en OTLP-exporter, og registrerer den globalt. Attributten service.name er afgørende — det er sådan, din tracing-backend grupperer spans.
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 configure_tracing() -> None:
resource = Resource.create({
"service.name": "orders-api",
"service.version": "1.4.0",
"deployment.environment": "production",
})
provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(endpoint="http://otel-collector:4317")
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)Automatisk instrumentering af FastAPI
FastAPIInstrumentor omslutter din app, så hver indgående anmodning automatisk bliver til en server-span. Den læser routen, metoden og statuskoden og — vigtigst af alt — udtrækker den indgående trace-kontekst fra anmodningens headere, så denne tjenestes spans knyttes til den kaldende tjenestes trace.
Kald configure_tracing() først, og instrumenter derefter app-instansen lige efter, at du har oprettet den. Rækkefølgen er vigtig: Provideren skal være indstillet globalt, før instrumenteringen læser den.
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from .tracing import configure_tracing
configure_tracing()
app = FastAPI(title="orders-api")
FastAPIInstrumentor.instrument_app(app)
@app.get("/orders/{order_id}")
async def get_order(order_id: int):
# This handler already runs inside an auto-created server span.
return {"order_id": order_id, "status": "shipped"}Videreførelse af trace-kontekst: W3C-headeren traceparent
Det, der forbinder spans på tværs af tjenester, er videreførelse af kontekst. OpenTelemetry bruger som standard standarden W3C Trace Context, der benytter en traceparent-HTTP-header:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
00— version4bf9...4736— det 16 byte lange trace-id (delt på tværs af alle tjenester)00f0...02b7— den kaldende parts overordnede span-id01— trace-flag (samplet bit)
På vej ud indsætter instrumenterede HTTP-klienter denne header. På vej ind udtrækker serverinstrumenteringen den. Det er sådan, en trace forbliver ubrudt på tværs af netværket.
Videreførelse gennem udgående HTTP-kald
Når din FastAPI-handler kalder en tjeneste længere nede i kæden, skal du bruge en instrumenteret HTTP-klient, så traceparent-headeren indsættes automatisk. Med httpx aktiverer du HTTPXClientInstrumentor én gang ved opstart.
Nu opretter hver udgående anmodning en klient-span, der er et barn af den aktuelle server-span, og den nedstrøms tjeneste fortsætter den samme trace.
import httpx
from fastapi import FastAPI
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
HTTPXClientInstrumentor().instrument()
app = FastAPI()
@app.get("/orders/{order_id}/full")
async def get_full_order(order_id: int):
async with httpx.AsyncClient(base_url="http://payments") as client:
# traceparent is injected automatically on this request.
resp = await client.get(f"/charges/{order_id}")
return {"order_id": order_id, "payment": resp.json()}Videreførelse gennem databasekald
Databaseforespørgsler er ofte den langsomste del af en anmodning, så du vil også have dem som spans. For SQLAlchemy opretter SQLAlchemyInstrumentor en span pr. sætning og registrerer SQL'en og databasesystemet som attributter.
Du skal instrumentere enginen (send engine=... for synkron brug eller den synkrone engine bag en asynkron engine). Disse databasespans bliver børn af den aktive anmodningsspan, så en langsom forespørgsel vises indlejret under den handler, der udløste den.
from sqlalchemy.ext.asyncio import create_async_engine
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
engine = create_async_engine("postgresql+asyncpg://app:secret@db/orders")
# For async engines, instrument the underlying sync engine.
SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine)
# Every statement run through this engine now emits a DB span
# nested under the current request span automatically.Oprettelse af manuelle spans til forretningslogik
Automatisk instrumentering dækker I/O-grænser, men din egen logik er usynlig. Tilføj manuelle spans omkring meningsfulde arbejdsenheder for at se, hvor tiden bruges. Hent en tracer fra den globale provider, og brug den som en konteksthåndtering.
Fordi spannen startes inde i den aktive anmodningskontekst, indlejres den automatisk under anmodningsspannen — du behøver ikke manuelt at forbinde en overordnet span.
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
def price_order(items: list[dict]) -> float:
with tracer.start_as_current_span("price_order") as span:
span.set_attribute("order.item_count", len(items))
subtotal = sum(i["price"] * i["qty"] for i in items)
tax = round(subtotal * 0.20, 2)
total = subtotal + tax
span.set_attribute("order.total", total)
return totalBerigelse af spans med attributter, hændelser og status
En span bliver nyttig, når den indeholder kontekst. Brug:
set_attribute(key, value)til søgbare tags (bruger-ID, lejer, antal elementer). Følg OTel's semantiske konventioner, hvor de findes.add_event(name, attributes)til tidsstemplede markører (f.eks. "cache_miss").set_status(Status(StatusCode.ERROR))ogrecord_exception(exc), når noget fejler, så spannen vises med rødt i din backend.
Anbring aldrig hemmeligheder eller fuldstændige personhenførbare oplysninger i attributter — mange kan læse traces.
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
tracer = trace.get_tracer(__name__)
def reserve_stock(sku: str, qty: int, available: int) -> None:
with tracer.start_as_current_span("reserve_stock") as span:
span.set_attribute("inventory.sku", sku)
span.set_attribute("inventory.requested_qty", qty)
if qty > available:
span.add_event("stock_shortfall", {"available": available})
exc = ValueError(f"Only {available} of {sku} in stock")
span.record_exception(exc)
span.set_status(Status(StatusCode.ERROR))
raise exc
span.set_status(Status(StatusCode.OK))Sampling: Styring af mængden af traces
Det er dyrt at trace hver anmodning i fuldt omfang. Sampling afgør, hvilke traces der skal gemmes. Den anbefalede head-baserede sampler er ParentBasedTraceIdRatioBased:
- Hvis en indgående anmodning allerede indeholder en sampling-beslutning (
01-flaget itraceparent), bliver den respekteret — så en trace enten gemmes eller kasseres konsekvent på tværs af alle tjenester. - For nye rod-anmodninger samples en fast andel (f.eks. 10 %).
Denne konsekvens er grunden til, at parent-baseret sampling er vigtig: Du ønsker aldrig, at tjeneste A gemmer en span, mens tjeneste B kasserer dens barn, så der opstår en brudt trace.
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.sampling import (
ParentBasedTraceIdRatioBased,
)
# Keep ~10% of root traces; honor upstream sampling decisions.
sampler = ParentBasedTraceIdRatioBased(rate=0.10)
provider = TracerProvider(sampler=sampler)Sammenkædning af logge med traces
Traces og logge er mest effektive sammen. Indsæt den aktuelle trace_id og span_id i hver loglinje, så du kan gå direkte fra en logpost til den fulde trace.
Du læser den aktive span-kontekst fra trace.get_current_span().get_span_context(). Når logningsinstrumenteringen er aktiveret, kan OTel også automatisk indsætte disse felter i den almindelige logpost.
import logging
from opentelemetry import trace
logger = logging.getLogger("orders")
def log_with_trace(message: str) -> None:
ctx = trace.get_current_span().get_span_context()
trace_id = format(ctx.trace_id, "032x")
span_id = format(ctx.span_id, "016x")
logger.info("%s", message, extra={
"trace_id": trace_id,
"span_id": span_id,
})Hurtig kontrol: Videreførelse på tværs af tjenester
Tjeneste A (FastAPI) modtager en anmodning og kalder tjeneste B over HTTP. Du vil have, at B's spans vises under den samme trace som A's. Hvilken mekanisme får dette til at fungere?
Opsummering
Du kan nu instrumentere en FastAPI-backend til distribueret sporing fra ende til anden:
- Grundopsæt en
TracerProvidermed enResource(indstilservice.name), enBatchSpanProcessorog en OTLP-exporter. - Instrumenter automatisk appen med
FastAPIInstrumentor, så hver anmodning bliver en server-span, der udtrækker indgående kontekst. - Viderefør kontekst gennem udgående HTTP (
HTTPXClientInstrumentor) og databasen (SQLAlchemyInstrumentor) — W3C-headerentraceparentholder tracen ubrudt. - Berig med manuelle spans, attributter, hændelser, status og registrerede undtagelser for din forretningslogik.
- Sample med
ParentBasedTraceIdRatioBasedfor konsekvente traces til en overkommelig pris, og sammenkæd logge via den aktivetrace_id/span_id.
Resultatet er, at ét klik fører dig fra en langsom anmodning til den præcise indlejrede span — handler, HTTP-kald eller forespørgsel — der forårsagede den.
Lær Bootcamp i FastAPI-backendudvikling med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 21
- Lektioner
- 84
Ofte stillede spørgsmål
Er lektionen “Distribueret tracing med OpenTelemetry” gratis?
Ja — alle 3 lektioner i læringssporet Bootcamp i FastAPI-backendudvikling, inklusive “Distribueret tracing med OpenTelemetry”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Bootcamp i FastAPI-backendudvikling-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Distribueret tracing med OpenTelemetry”?
Instrumentér automatisk FastAPI, og viderefør trace-kontekst gennem downstream-HTTP- og databasekald. Du øver dig i Bootcamp i FastAPI-backendudvikling med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Bootcamp i FastAPI-backendudvikling?
Der kræves ingen tidligere erfaring. Bootcamp i FastAPI-backendudvikling på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.
Hvor lang tid tager lektionen “Distribueret tracing med OpenTelemetry”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Bootcamp i FastAPI-backendudvikling-lektion?
Ja. Alle Bootcamp i FastAPI-backendudvikling-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Struktureret JSON-logging og korrelations-id'er
- Distribueret tracing med OpenTelemetry
- Prometheus-metrics og RED/USE-dashboards
- Alarmering på SLO'er og error budgets