feat: structured auth logging + log analytics foundation
Add _auth_label() classifier: local/anon/guest/authed — no PII, just enough to distinguish traffic types in docker logs for log-based analytics. Instrument /api/session: logs new_guest (with UUID) or auth=.../tier=... on every session bootstrap. Instrument /api/search: expands existing multi-search log line with auth=, tier=, adapter=, pages=, queries=, listings= fields for grep/awk analysis of search behaviour by tier. Add logging.basicConfig so app-level log.info() calls appear in docker logs alongside the Uvicorn access log (previously suppressed by missing root handler).
This commit is contained in:
parent
8e40e51ed3
commit
873b9a1413
1 changed files with 34 additions and 1 deletions
35
api/main.py
35
api/main.py
|
|
@ -38,8 +38,34 @@ from app.platforms.ebay.scraper import ScrapedEbayAdapter
|
|||
from app.trust import TrustScorer
|
||||
|
||||
load_env(Path(".env"))
|
||||
|
||||
# Wire the app logger into Uvicorn's handler chain so application-level
|
||||
# log.info() calls appear in docker logs alongside the access log.
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(levelname)s:%(name)s: %(message)s",
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _auth_label(user_id: str) -> str:
|
||||
"""Classify a user_id into a short tag for structured log lines.
|
||||
|
||||
Intentionally coarse — no PII, just enough to distinguish traffic types:
|
||||
local → local dev instance (not cloud)
|
||||
anon → fresh visitor, no cookie yet
|
||||
guest → returning anonymous visitor with snipe_guest cookie
|
||||
authed → authenticated Directus account
|
||||
"""
|
||||
if user_id == "local":
|
||||
return "local"
|
||||
if user_id == "anonymous":
|
||||
return "anon"
|
||||
if user_id.startswith("guest:"):
|
||||
return "guest"
|
||||
return "authed"
|
||||
|
||||
|
||||
# ── SSE update registry ───────────────────────────────────────────────────────
|
||||
# Maps session_id → SimpleQueue of update events.
|
||||
# SimpleQueue is always thread-safe; no asyncio loop needed to write from threads.
|
||||
|
|
@ -222,6 +248,9 @@ def session_info(response: Response, session: CloudUser = Depends(get_session)):
|
|||
shared_db=session.shared_db,
|
||||
user_db=session.user_db,
|
||||
)
|
||||
log.info("session new_guest user_id=%s", guest_uuid)
|
||||
else:
|
||||
log.info("session auth=%s tier=%s", _auth_label(session.user_id), session.tier)
|
||||
features = compute_features(session.tier)
|
||||
return {
|
||||
"user_id": session.user_id,
|
||||
|
|
@ -524,7 +553,11 @@ def search(
|
|||
log.warning("eBay scrape failed: %s", e)
|
||||
raise HTTPException(status_code=502, detail=f"eBay search failed: {e}")
|
||||
|
||||
log.info("Multi-search: %d queries → %d unique listings", len(ebay_queries), len(listings))
|
||||
log.info(
|
||||
"search auth=%s tier=%s adapter=%s pages=%d queries=%d listings=%d q=%r",
|
||||
_auth_label(session.user_id), session.tier, adapter_used,
|
||||
pages, len(ebay_queries), len(listings), q,
|
||||
)
|
||||
|
||||
# Main-thread stores — fresh connections, same thread.
|
||||
# shared_store: sellers, market_comps (all users share this data)
|
||||
|
|
|
|||
Loading…
Reference in a new issue