"""Request ID + structured access logging for production operations.""" from __future__ import annotations import json import logging import time import uuid from typing import Callable from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response logger = logging.getLogger("cepheus.access") class RequestContextMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next: Callable) -> Response: request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4()) request.state.request_id = request_id start = time.perf_counter() status_code = 500 try: response = await call_next(request) status_code = response.status_code response.headers["X-Request-ID"] = request_id return response finally: duration_ms = round((time.perf_counter() - start) * 1000, 2) log_entry = { "request_id": request_id, "method": request.method, "path": request.url.path, "status": status_code, "duration_ms": duration_ms, "client": request.client.host if request.client else None, } if status_code >= 500: logger.error(json.dumps(log_entry)) elif status_code >= 400: logger.warning(json.dumps(log_entry)) else: logger.info(json.dumps(log_entry))