From dde7d4fa30857b22ab281b4845e2790ff3dd8c77 Mon Sep 17 00:00:00 2001 From: Robert Helewka Date: Wed, 6 May 2026 20:06:17 -0400 Subject: [PATCH] log: gate stderr handler behind PALLAS_LOG_STDERR so fast-agent TUI is usable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stderr StreamHandler, even using sys.__stderr__ captured before Rich installed its Live display, still corrupts the fast-agent TUI in interactive 'fast-agent go' sessions — Rich redraws on top of our JSON log lines but leaks through every repaint, making the interface effectively unusable. Keep the RotatingFileHandler as the always-on durable capture (that's what survives fast-agent's progress_display takeover and what we rely on for diagnostics). Gate the stderr sink behind PALLAS_LOG_STDERR=1 for operators who explicitly want journal/terminal capture on a systemd-managed deployment. --- pallas/log.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) 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