python 36 lines · 7 steps

Request ID tracing in FastAPI middleware

Assign every request a unique ID, stash it in a ContextVar, and thread it through logs and responses.

Explained by highlit
1import logging
2import uuid
3from contextvars import ContextVar
4 
5from starlette.middleware.base import BaseHTTPMiddleware
6from starlette.requests import Request
7from starlette.types import ASGIApp
8 
9request_id_ctx: ContextVar[str] = ContextVar("request_id", default="-")
10 
11 
12class RequestIDMiddleware(BaseHTTPMiddleware):
13 def __init__(self, app: ASGIApp, header_name: str = "X-Request-ID") -> None:
14 super().__init__(app)
15 self.header_name = header_name
16 
17 async def dispatch(self, request: Request, call_next):
18 request_id = request.headers.get(self.header_name) or uuid.uuid4().hex
19 token = request_id_ctx.set(request_id)
20 request.state.request_id = request_id
21 try:
22 response = await call_next(request)
23 finally:
24 request_id_ctx.reset(token)
25 response.headers[self.header_name] = request_id
26 return response
27 
28 
29class RequestIDLogFilter(logging.Filter):
30 def filter(self, record: logging.LogRecord) -> bool:
31 record.request_id = request_id_ctx.get()
32 return True
33 
34 
35def get_request_id() -> str:
36 return request_id_ctx.get()
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A ContextVar carries per-request state safely across async boundaries without passing it through every function.
  2. 2Always reset a ContextVar with its token in a finally block so leaked values never contaminate later requests.
  3. 3A logging filter can enrich every record with contextual data pulled from the current execution context.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Request ID tracing in FastAPI middleware — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code