from __future__ import annotations import logging import time import uuid from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from starlette.requests import Request from starlette.responses import Response from app.core.logging import get_logger logger = get_logger("app.middleware.access") _SKIP_PATHS: frozenset[str] = frozenset({"/", "/docs", "/openapi.json", "/redoc"}) class AccessLogMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: if request.url.path in _SKIP_PATHS: return await call_next(request) request_id = request.headers.get("X-Request-ID", uuid.uuid4().hex[:12]) start = time.perf_counter() response = await call_next(request) duration_ms = (time.perf_counter() - start) * 1000 level = logging.WARNING if response.status_code >= 500 else logging.INFO logger.log( level, "%s %s %s %.1fms request_id=%s", request.method, request.url.path, response.status_code, duration_ms, request_id, ) response.headers["X-Request-ID"] = request_id return response