Files
TieMeasureFlow/src/backend/api/middleware/logging.py
T
Adriano 5ca3dc14cd fix(logging): action troncata a 100, ip_address dal client IP proxy-aware (#36 punto 6)
action era action=f"{method} {path}" senza limite: un path lungo falliva
l'INSERT (String(100)), inghiottito dal try/except generico, e la riga di
audit spariva in silenzio. Troncata a 100 caratteri. ip_address usava
request.client.host, che dietro Nginx/Flask e' sempre il proxy: ora usa
RateLimitMiddleware._client_ip(request), lo stesso risolutore proxy-aware
del rate limiter (X-Forwarded-For con trusted_proxy_count).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:42:16 +02:00

61 lines
2.3 KiB
Python

"""Access logging middleware for FastAPI."""
import time
from typing import Callable
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from sqlalchemy import insert
from src.backend.api.middleware.rate_limit import RateLimitMiddleware
from src.backend.database import async_session_factory
from src.backend.models.orm.access_log import AccessLog
# AccessLog.action is String(100): a long path (with query-string-like
# segments) silently failed the insert before this truncation existed.
_ACTION_MAX_LENGTH = 100
class AccessLogMiddleware(BaseHTTPMiddleware):
"""Middleware that logs every API request to the access_logs table."""
# Paths to exclude from logging (health checks, static files)
EXCLUDED_PATHS = {"/api/health", "/docs", "/openapi.json", "/redoc"}
async def dispatch(self, request: Request, call_next: Callable) -> Response:
# Skip excluded paths
if request.url.path in self.EXCLUDED_PATHS:
return await call_next(request)
start_time = time.time()
response = await call_next(request)
duration_ms = (time.time() - start_time) * 1000
# Extract user info from request state (set by auth middleware)
user_id = getattr(request.state, "user_id", None)
# Log asynchronously (don't block response)
try:
action = f"{request.method} {request.url.path}"[:_ACTION_MAX_LENGTH]
async with async_session_factory() as session:
await session.execute(
insert(AccessLog).values(
user_id=user_id,
action=action,
details={
"method": request.method,
"path": request.url.path,
"query": str(request.query_params),
"status_code": response.status_code,
"duration_ms": round(duration_ms, 2),
},
ip_address=RateLimitMiddleware._client_ip(request),
user_agent=request.headers.get("user-agent", "")[:500],
)
)
await session.commit()
except Exception:
# Don't fail the request if logging fails
pass
return response