Add structured logging throughout the backend

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>
This commit is contained in:
vrubelroman 2026-07-09 20:10:10 +00:00
parent 188bf01d6e
commit be67902b45
11 changed files with 114 additions and 4 deletions

View file

@ -1,3 +1,5 @@
import logging
from fastapi import APIRouter, Depends
from geoalchemy2 import Geometry
from sqlalchemy import cast, func, select
@ -8,6 +10,8 @@ from app.models.city import City
from app.schemas.city import CityOut
from app.schemas.common import LatLon, LocalizedName
logger = logging.getLogger("guidecity.cities")
router = APIRouter(tags=["cities"])
@ -31,4 +35,5 @@ def list_cities(db: Session = Depends(get_db)) -> list[CityOut]:
center=center,
)
)
logger.debug("listed %d cities", len(out))
return out

View file

@ -1,3 +1,5 @@
import logging
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
@ -8,6 +10,8 @@ from app.schemas.nearby import NearbyPlace, NearbyResponse
from app.schemas.place import ContentOut
from app.services.nearby_search import find_nearby
logger = logging.getLogger("guidecity.nearby")
router = APIRouter(tags=["nearby"])
@ -32,6 +36,9 @@ def nearby(
),
db: Session = Depends(get_db),
) -> NearbyResponse:
if speed is not None or heading is not None:
logger.debug("nearby called with reserved v2 params speed=%s heading=%s (ignored)", speed, heading)
result = find_nearby(
db,
lat=lat,

View file

@ -1,3 +1,5 @@
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from geoalchemy2 import Geometry
from sqlalchemy import and_, cast, func, select
@ -13,6 +15,8 @@ from app.schemas.common import LatLon
from app.schemas.place import ContentOut, PlaceDetail, PlaceListItem
from app.services.geocoding import search_places
logger = logging.getLogger("guidecity.places")
router = APIRouter(tags=["places"])
@ -56,6 +60,7 @@ def get_place(
)
row = db.execute(stmt).first()
if row is None:
logger.warning("place_id=%d not found for lang=%s length=%s", place_id, lang, length)
raise HTTPException(status_code=404, detail="Place not found")
place, content, lon, lat = row
@ -84,6 +89,7 @@ def list_city_places(
) -> list[PlaceListItem]:
city = db.scalar(select(City).where(City.slug == city_slug))
if city is None:
logger.warning("city_slug=%r not found", city_slug)
raise HTTPException(status_code=404, detail="City not found")
stmt = (
@ -111,6 +117,7 @@ def list_city_places(
stmt = stmt.where(PlaceContent.title.ilike(f"%{q}%"))
rows = db.execute(stmt).all()
logger.debug("city_slug=%r listing -> %d place(s)", city_slug, len(rows))
return [
PlaceListItem(id=p.id, slug=p.slug, category=p.category, name=pc.title, location=LatLon(lat=lat, lon=lon))
for p, pc, lon, lat in rows