Короткий ответ
Middleware при входе запроса либо берёт готовый ID из заголовка (например, X-Request-ID, если он пришёл от прокси/клиента), либо генерирует новый uuid4, кладёт его в контекст логирования (например, через contextvars) и добавляет этот же ID в заголовок ответа и во все исходящие вызовы к другим сервисам.
Как это работает подробнее
Request ID должен быть доступен не только в самом middleware, но и глубже — в бизнес-логике, логировании, вызовах к БД/внешним API. Для этого обычно используют contextvars.ContextVar (в async-коде) или thread-local (в синхронном), чтобы не передавать ID явным параметром через все функции.
Пример кода
example.py
import uuid
import contextvars
from fastapi import FastAPI, Request
request_id_var = contextvars.ContextVar("request_id", default=None)
app = FastAPI()
@app.middleware("http")
async def add_request_id(request: Request, call_next):
rid = request.headers.get("X-Request-ID", str(uuid.uuid4()))
request_id_var.set(rid)
response = await call_next(request)
response.headers["X-Request-ID"] = rid
return response
# В логах/обработчиках: logger.info("", extra={"request_id": request_id_var.get()})Что использовать на практике
- Практика. Этот же ID передают дальше при вызове других сервисов (например, в заголовке
X-Request-IDисходящего HTTP-запроса), чтобы можно было сквозно искать все логи, относящиеся к одному запросу пользователя, по цепочке сервисов — это особенно важно при диагностике проблем в распределённой системе.
Подводные камни
- Риск. Генерируют новый ID на каждом внутреннем слое вместо того, чтобы пронести один и тот же через всю цепочку; забывают очищать
ContextVarмежду запросами в приложениях с пулом воркеров, из-за чего значение может «подтекать» между независимыми запросами при неправильном использовании контекста.