Coverage for src/lumen/core/logging_middleware.py: 83%
24 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-08-31 18:46 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-08-31 18:46 +0000
1"""
2This middleware handles access logging for all HTTP requests.
3It extracts details like timing and client IP to output structured logs.
4"""
6import time
7from collections.abc import Awaitable, Callable
9import structlog
10from fastapi import Request, Response
11from starlette.middleware.base import BaseHTTPMiddleware
13from .context import current_client_ip
15logger = structlog.get_logger("access")
18class AccessLogMiddleware(BaseHTTPMiddleware):
19 """
20 - Middleware to log access requests and responses
21 - It logs the HTTP method, path, status code, duration, and client IP address
22 - checks requests and responses for exceptions and logs them accordingly
23 """
25 async def dispatch(
26 self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
27 ) -> Response:
28 start_time = time.perf_counter()
29 structlog.contextvars.clear_contextvars()
31 client_host = request.client.host if request.client else None
32 ip_token = current_client_ip.set(client_host)
34 try:
35 try:
36 response = await call_next(request)
37 process_time = time.perf_counter() - start_time
39 logger.info(
40 "http.request",
41 method=request.method,
42 path=request.url.path,
43 status_code=response.status_code,
44 duration_ms=round(process_time * 1000, 2),
45 client_ip=request.client.host if request.client else None,
46 )
47 return response
48 except Exception as e:
49 process_time = time.perf_counter() - start_time
50 logger.error(
51 "http.request.error",
52 method=request.method,
53 path=request.url.path,
54 duration_ms=round(process_time * 1000, 2),
55 client_ip=request.client.host if request.client else None,
56 exc_info=e,
57 )
58 raise
59 finally:
60 current_client_ip.reset(ip_token)