From 873b9a141390a8ed6ff1535ced51b81776590382 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Thu, 16 Apr 2026 13:04:46 -0700 Subject: [PATCH] feat: structured auth logging + log analytics foundation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- api/main.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/api/main.py b/api/main.py index 86eee40..6a6d534 100644 --- a/api/main.py +++ b/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)