diff --git a/pallas/log.py b/pallas/log.py index 6e74854..9db560e 100644 --- a/pallas/log.py +++ b/pallas/log.py @@ -171,10 +171,15 @@ def setup_logging() -> None: stdout and swallows plain ``print`` / stderr writes mid-render, so a file sink is the only guaranteed way to see DEBUG diagnostics and tracebacks. - * ``StreamHandler`` → ``sys.__stderr__``. Uses the *original* stderr - file descriptor captured before Rich installed its Live display, so - records bypass the progress renderer and land in the terminal or - journal verbatim. Fine for production where Rich is off. + * ``StreamHandler`` → ``sys.__stderr__`` (opt-in). Uses the *original* + stderr FD captured before Rich installed its Live display, so records + would bypass the progress renderer and land in the terminal or journal + verbatim. In interactive ``fast-agent go`` sessions this still + smashes the Rich UI (Rich redraws on top but our JSON lines leak + through every repaint), so we only attach it when ``PALLAS_LOG_STDERR`` + is set to a truthy value — production systemd deployments that want + journal capture should set ``PALLAS_LOG_STDERR=1``; interactive users + get a clean TUI and rely on the rotating file sink. - ``httpx`` / ``httpcore``: WARNING (prevent request-level debug flooding) - ``uvicorn.access``: health path filter applied @@ -209,12 +214,17 @@ def setup_logging() -> None: "could not attach file log handler: %s", exc ) - # Stderr sink — writes to the *original* FD Rich captured at import - # time, so our records skip the progress display and hit the TTY - # (or systemd journal) directly. - stream_handler = logging.StreamHandler(stream=sys.__stderr__) - stream_handler.setFormatter(formatter) - handlers.append(stream_handler) + # Stderr sink — *opt-in* only. Interactive ``fast-agent go`` sessions + # install Rich's Live display which owns the terminal; emitting JSON + # log records to stderr during that session corrupts the TUI on every + # redraw and made Pallas effectively unusable in dev. Operators who + # want systemd/journal capture can re-enable by exporting + # ``PALLAS_LOG_STDERR=1`` before launching. The rotating file sink + # above is the always-on durable capture. + if os.environ.get("PALLAS_LOG_STDERR", "").lower() in ("1", "true", "yes"): + stream_handler = logging.StreamHandler(stream=sys.__stderr__) + stream_handler.setFormatter(formatter) + handlers.append(stream_handler) # Root logger carries everything from libraries we do NOT own. # We set the level low enough to accept our configured level; each