from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response import time import logging import uuid logger = logging.getLogger("structured") class RequestContextMiddleware(BaseHTTPMiddleware): """Adds a unique request ID and logs request / response details.""" async def dispatch(self, request: Request, call_next): request_id = str(uuid.uuid4()) start_time = time.time() # Attach id to request state for downstream handlers request.state.request_id = request_id logger.info( { "event": "request_start", "request_id": request_id, "method": request.method, "path": request.url.path, "client": request.client.host if request.client else None, } ) response: Response = await call_next(request) process_time = (time.time() - start_time) * 1000 logger.info( { "event": "request_end", "request_id": request_id, "status_code": response.status_code, "duration_ms": round(process_time, 2), } ) # Propagate request id header for tracing response.headers["X-Request-ID"] = request_id return response