From cfc28d7002c3538bc587043a5251070651f1113d Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Wed, 10 Jun 2026 10:42:30 -0700 Subject: [PATCH] fix(slice-2): surface ecomm.* INFO logs so the one-time code reaches the dev log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uvicorn configures only its own loggers, so the LogMailer INFO line — PUC-10's local dev channel (the code in the backend log) — was swallowed. Add an idempotent dedicated handler for the 'ecomm' logger (propagate=False) in create_app so dev.sh/uvicorn runs show the code. Caught by the SLICE-2 end-to-end walk; unit tests read the in-memory outbox and didn't. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/app/main.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/backend/app/main.py b/backend/app/main.py index 459466d..3f89071 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -8,6 +8,8 @@ storefronts domain (SLICE-3) replaces the _storefront_for seam. """ from __future__ import annotations +import logging +import sys from contextlib import asynccontextmanager from typing import Any @@ -47,6 +49,23 @@ def _storefront_for(conn: psycopg.Connection, account: accounts.Account) -> dict return None +def _ensure_app_logging() -> None: + """Surface the app's own `ecomm.*` INFO logs on stderr (idempotent). + + uvicorn configures only its own loggers, leaving the root with no INFO handler — so + without this the `ecomm.mailer` line (PUC-10's local dev channel: the one-time code in + the backend log) would be swallowed. A dedicated handler with propagate=False keeps it + out of uvicorn's stream and avoids double-logging. + """ + lg = logging.getLogger("ecomm") + if not lg.handlers: + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(logging.Formatter("%(levelname)s:%(name)s: %(message)s")) + lg.addHandler(handler) + lg.setLevel(logging.INFO) + lg.propagate = False + + def _set_session_cookie(response: Response, account: accounts.Account) -> None: token = session_mod.sign({"account_id": account.id, "email": account.email}, config.session_secret()) response.set_cookie( @@ -60,6 +79,7 @@ def _set_session_cookie(response: Response, account: accounts.Account) -> None: def create_app(database_url: str | None = None) -> FastAPI: + _ensure_app_logging() dsn = database_url or config.database_url() @asynccontextmanager