import json import logging import time from collections.abc import Callable from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response logger = logging.getLogger("speech_machine.access") class StructuredLoggingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next: Callable) -> Response: start = time.perf_counter() response = await call_next(request) duration_ms = round((time.perf_counter() - start) * 1000, 2) payload = { "method": request.method, "path": request.url.path, "status": response.status_code, "duration_ms": duration_ms, "client_ip": request.client.host if request.client else None, } if response.status_code >= 500: logger.error(json.dumps(payload)) elif response.status_code >= 400: logger.warning(json.dumps(payload)) else: logger.info(json.dumps(payload)) return response