""" Request ID Middleware for distributed tracing Generates and propagates unique request IDs across all services """ import uuid import structlog from fastapi import Request from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import Response from app.core.header_manager import header_manager logger = structlog.get_logger() class RequestIDMiddleware(BaseHTTPMiddleware): """ Middleware to generate and propagate request IDs for distributed tracing. Request IDs are: - Generated if not provided by client - Logged with every request - Propagated to all downstream services - Returned in response headers """ def __init__(self, app): super().__init__(app) async def dispatch(self, request: Request, call_next) -> Response: """Process request with request ID tracking""" # Extract or generate request ID request_id = request.headers.get("X-Request-ID") if not request_id: request_id = str(uuid.uuid4()) # Store in request state for access by routes request.state.request_id = request_id # Bind request ID to structured logger context logger_ctx = logger.bind(request_id=request_id) # Inject request ID header for downstream services using HeaderManager # Note: This runs early in middleware chain, so we use add_header_for_middleware header_manager.add_header_for_middleware(request, "x-request-id", request_id) # Log request start logger_ctx.info( "Request started", method=request.method, path=request.url.path, client_ip=request.client.host if request.client else None ) try: # Process request response = await call_next(request) # Add request ID to response headers response.headers["X-Request-ID"] = request_id # Log request completion logger_ctx.info( "Request completed", method=request.method, path=request.url.path, status_code=response.status_code ) return response except Exception as e: # Log request failure logger_ctx.error( "Request failed", method=request.method, path=request.url.path, error=str(e), error_type=type(e).__name__ ) raise