Files
OCR/docengine/app/middleware/audit.py
2026-06-01 21:49:53 +05:30

72 lines
2.2 KiB
Python

from __future__ import annotations
import time
import uuid
from typing import Any
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from app.core.logging_config import get_logger
logger = get_logger(__name__)
class AuditMiddleware(BaseHTTPMiddleware):
"""Middleware to log all API requests for audit purposes."""
EXCLUDED_PATHS = {"/api/v1/health", "/metrics", "/docs", "/openapi.json", "/redoc"}
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
if request.url.path in self.EXCLUDED_PATHS:
return await call_next(request)
request_id = str(uuid.uuid4())
start_time = time.monotonic()
# Extract client info
client_ip = request.client.host if request.client else "unknown"
user_agent = request.headers.get("user-agent", "unknown")
# Add request ID to request state
request.state.request_id = request_id
logger.info(
"request_started",
request_id=request_id,
method=request.method,
path=request.url.path,
client_ip=client_ip,
user_agent=user_agent[:200],
)
try:
response = await call_next(request)
duration_ms = (time.monotonic() - start_time) * 1000
logger.info(
"request_completed",
request_id=request_id,
method=request.method,
path=request.url.path,
status_code=response.status_code,
duration_ms=round(duration_ms, 2),
client_ip=client_ip,
)
response.headers["X-Request-ID"] = request_id
response.headers["X-Process-Time"] = f"{duration_ms:.2f}ms"
return response
except Exception as exc:
duration_ms = (time.monotonic() - start_time) * 1000
logger.exception(
"request_failed",
request_id=request_id,
method=request.method,
path=request.url.path,
duration_ms=round(duration_ms, 2),
error=str(exc),
)
raise