Request-level logging middleware (method/path/status/duration), per-module loggers in routes/services (nearby search radius expansion, 404s, name search), LOG_LEVEL setting wired through docker-compose, and seed_loader converted from print() to logging. Verified: pytest passes, and manually confirmed log lines appear for both successful and 404 requests via `docker compose logs api`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
37 lines
1.1 KiB
Python
37 lines
1.1 KiB
Python
import logging
|
|
import time
|
|
from collections.abc import Awaitable, Callable
|
|
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response
|
|
|
|
request_logger = logging.getLogger("guidecity.http")
|
|
|
|
|
|
def configure_logging(level: int = logging.INFO) -> None:
|
|
logging.basicConfig(
|
|
level=level,
|
|
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
|
)
|
|
|
|
|
|
async def log_requests(
|
|
request: Request,
|
|
call_next: Callable[[Request], Awaitable[Response]],
|
|
) -> Response:
|
|
"""Logs method/path/status/duration for every request handled by the app."""
|
|
start = time.perf_counter()
|
|
try:
|
|
response = await call_next(request)
|
|
except Exception:
|
|
duration_ms = (time.perf_counter() - start) * 1000
|
|
request_logger.exception(
|
|
"%s %s -> unhandled exception (%.1fms)", request.method, request.url.path, duration_ms
|
|
)
|
|
raise
|
|
|
|
duration_ms = (time.perf_counter() - start) * 1000
|
|
request_logger.info(
|
|
"%s %s -> %d (%.1fms)", request.method, request.url.path, response.status_code, duration_ms
|
|
)
|
|
return response
|