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:
parent
188bf01d6e
commit
be67902b45
11 changed files with 114 additions and 4 deletions
|
|
@ -4,6 +4,8 @@ Used when the user denies location permission and types a place of interest,
|
|||
or when 2GIS's own geocoding API isn't wired into a given client flow yet.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from geoalchemy2 import Geometry
|
||||
from sqlalchemy import and_, cast, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
|
@ -13,6 +15,8 @@ from app.models.enums import ContentLanguage, ContentLength
|
|||
from app.models.place import Place
|
||||
from app.models.place_content import PlaceContent
|
||||
|
||||
logger = logging.getLogger("guidecity.geocoding")
|
||||
|
||||
|
||||
def search_places(
|
||||
db: Session,
|
||||
|
|
@ -44,4 +48,6 @@ def search_places(
|
|||
if city_slug is not None:
|
||||
stmt = stmt.join(City, City.id == Place.city_id).where(City.slug == city_slug)
|
||||
|
||||
return db.execute(stmt).all()
|
||||
results = db.execute(stmt).all()
|
||||
logger.info("name search q=%r city_slug=%s lang=%s -> %d result(s)", q, city_slug, language, len(results))
|
||||
return results
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ results toward the user's direction of travel and to pick a shorter content
|
|||
Not implemented in this iteration — callers should not pass them yet.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
from geoalchemy2 import Geometry
|
||||
|
|
@ -21,6 +22,8 @@ from app.models.enums import ContentLanguage, ContentLength
|
|||
from app.models.place import Place
|
||||
from app.models.place_content import PlaceContent
|
||||
|
||||
logger = logging.getLogger("guidecity.nearby_search")
|
||||
|
||||
|
||||
@dataclass
|
||||
class NearbyResult:
|
||||
|
|
@ -44,6 +47,14 @@ def find_nearby(
|
|||
city_id: int | None = None
|
||||
if city_slug is not None:
|
||||
city_id = db.scalar(select(City.id).where(City.slug == city_slug))
|
||||
if city_id is None:
|
||||
logger.warning("city_slug=%r not found; searching across all cities", city_slug)
|
||||
|
||||
logger.debug(
|
||||
"nearby search starting: lat=%.5f lon=%.5f city_slug=%s lang=%s length=%s "
|
||||
"min_results=%d initial_radius_m=%d step_m=%d max_radius_m=%d",
|
||||
lat, lon, city_slug, language, length, min_results, initial_radius_m, step_m, max_radius_m,
|
||||
)
|
||||
|
||||
origin = func.ST_GeogFromText(f"SRID=4326;POINT({lon} {lat})")
|
||||
distance_expr = func.ST_Distance(Place.location, origin)
|
||||
|
|
@ -75,8 +86,16 @@ def find_nearby(
|
|||
while True:
|
||||
stmt = base_stmt.where(func.ST_DWithin(Place.location, origin, radius))
|
||||
rows = db.execute(stmt).all()
|
||||
logger.debug("radius=%dm -> %d place(s) found", radius, len(rows))
|
||||
if len(rows) >= min_results or radius >= max_radius_m:
|
||||
break
|
||||
radius += step_m
|
||||
|
||||
if len(rows) < min_results:
|
||||
logger.warning(
|
||||
"hit max_radius_m=%dm with only %d/%d place(s) found near lat=%.5f lon=%.5f",
|
||||
max_radius_m, len(rows), min_results, lat, lon,
|
||||
)
|
||||
logger.info("nearby search done: radius=%dm count=%d", radius, len(rows))
|
||||
|
||||
return NearbyResult(search_radius_m=radius, rows=rows)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue