Bootcamp i FastAPI-backendudvikling · Lektion

Distribueret tracing med OpenTelemetry

Instrumentér automatisk FastAPI, og viderefør trace-kontekst gennem downstream-HTTP- og databasekald.

Lektion 2 af 413 trin

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), events og en status.
  • SpanProcessor — samler afsluttede spans i grupper (brug BatchSpanProcessor i 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-api
  • opentelemetry-exporter-otlp
  • opentelemetry-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 — version
  • 4bf9...4736 — det 16 byte lange trace-id (delt på tværs af alle tjenester)
  • 00f0...02b7 — den kaldende parts overordnede span-id
  • 01 — 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 total

Berigelse 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)) og record_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 i traceparent), 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 TracerProvider med en Resource (indstil service.name), en BatchSpanProcessor og 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-headeren traceparent holder tracen ubrudt.
  • Berig med manuelle spans, attributter, hændelser, status og registrerede undtagelser for din forretningslogik.
  • Sample med ParentBasedTraceIdRatioBased for konsekvente traces til en overkommelig pris, og sammenkæd logge via den aktive trace_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.

Gratis at komme i gang

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

  1. Struktureret JSON-logging og korrelations-id'er
  2. Distribueret tracing med OpenTelemetry
  3. Prometheus-metrics og RED/USE-dashboards
  4. Alarmering på SLO'er og error budgets
← Tilbage til Bootcamp i FastAPI-backendudvikling