File size: 1,952 Bytes
aa27d2d
817ad83
aa27d2d
 
 
817ad83
 
 
aa27d2d
 
 
817ad83
 
 
aa27d2d
 
 
817ad83
 
 
 
 
 
 
 
 
aa27d2d
817ad83
 
aa27d2d
817ad83
 
aa27d2d
817ad83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import time
import os
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from app.core.logger import logger

# Paths that are too noisy to log at INFO β€” only log if they error
_QUIET_PATHS = frozenset(["/health", "/favicon.ico", "/metrics", "/docs", "/openapi.json", "/redoc"])

class APILoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start_time = time.perf_counter()
        path = request.url.path
        method = request.method

        try:
            response = await call_next(request)
        except Exception as exc:
            duration_ms = round((time.perf_counter() - start_time) * 1000, 1)
            logger.bind(
                method=method,
                path=path,
                status=500,
                duration_ms=duration_ms,
            ).error(f"Unhandled exception on {method} {path} β€” {type(exc).__name__}: {exc}")
            raise

        duration_ms = round((time.perf_counter() - start_time) * 1000, 1)
        status = response.status_code

        # Suppress noisy health-check / static paths unless they error
        if path in _QUIET_PATHS and status < 400:
            return response

        # Route logs by severity so production dashboards can filter cleanly
        bound = logger.bind(method=method, path=path, status=status, duration_ms=duration_ms)

        if status >= 500:
            bound.error(f"{method} {path} β†’ {status} ({duration_ms}ms)")
        elif status >= 400:
            # 401 on /auth/me is expected background noise β€” demote to DEBUG
            if path == "/auth/me" and status == 401:
                bound.debug(f"{method} {path} β†’ {status} ({duration_ms}ms)")
            else:
                bound.warning(f"{method} {path} β†’ {status} ({duration_ms}ms)")
        else:
            bound.info(f"{method} {path} β†’ {status} ({duration_ms}ms)")

        return response