Pydantic · LogfireOpen PR Assigned

Tracing FastAPI background tasks

Separate, linked traces for FastAPI background-task batches, so request and background work can be sampled independently.

A request can finish before its background work starts. That becomes an observability problem when tail sampling has already discarded the request trace: later spans can bypass its sampling decision and arrive with missing parents.

This contribution addresses the FastAPI part of that problem. The proposed change creates a separate root trace for each non-empty background-task batch, and links it to the original request span.

Linked work, independent sampling

A span link preserves the relationship without making the background batch a child of an already-finished request trace. The request and background work can then have independent keep-or-discard decisions. Callbacks within the same batch share one background-trace decision.

Logfire illustration: a request trace linked to a separate background-task trace
Separate traces preserve a span link while keeping independent sampling decisions.

That separation also lets a background error retain earlier buffered records from its own trace. The request trace does not have to be retained for the background trace to remain useful. A link can legitimately point to a request span that sampling discarded.

Change the trace boundary, preserve execution

The wrapper runs the original task batch rather than replacing its callback machinery. Ordering remains unchanged, synchronous callbacks still use Starlette’s thread pool, and exceptions and cancellation continue to propagate.

Proposed batch wrapper · Python
token = context_api.attach(trace_api.set_span_in_context(trace_api.INVALID_SPAN))
try:
    with self.logfire_instance.span('FastAPI background tasks', _links=[(self.request_context, None)]):
        return await self.original_tasks()
finally:
    context_api.detach(token)

Clearing the active span starts a new root while keeping the surrounding telemetry context. The finally block restores the previous context. The patch preserves baggage, instrumentation suppression, and FastAPI’s own background-operation spans as children of the new trace.

Empty batches delegate directly to the original implementation; they do not create an extra trace.

Regression tests around the boundary

Coverage exercises all four combinations of retaining or discarding the request and background traces. It checks distinct trace IDs, request links, parent relationships, completed spans, background errors, synchronous and asynchronous callbacks, failures, cancellation, and context restoration.

Reported local validationResult
Focused background-task and tail-sampling tests35 passed
Broader relevant run before final test additions72 passed · 6 existing skips
Targeted fastapi.py coverage100%
Ruff checks, formatting, Linux-targeted PyrightPassed

These are the validation results reported in the PR, not a claim that the full upstream CI matrix has completed.

A deliberately narrow scope

The implementation covers FastAPI BackgroundTasks, including injected batches and directly returned responses carrying one. Plain Starlette background tasks and generic asyncio tasks remain outside this patch.

View implementation & discussion ↗
PR status checked .
Mahaboob AshrafSoftware & AI Engineer · Hyderabad, India

Frequently asked questions

Why use a span link instead of a parent span?

The background batch needs its own root and sampling decision. A span link keeps its relationship to the request without putting it back inside the request’s trace.

Does every background callback get a separate trace?

No. One non-empty FastAPI batch creates one background trace. Its callbacks share that trace’s sampling decision.

Does this apply to plain Starlette or asyncio tasks?

No. The patch is scoped to FastAPI BackgroundTasks. The sampling guide documents the boundary and the manual alternative for other cases.

LET’S CONNECT

LET’S TALK ABOUTWHAT’S NEXT.

I’m open to contracts, internships, and remote engineering roles.

STRAIGHT TO MY INBOX