popout/window: route adapter logger to stderr for terminal capture

Follow-up to commit 14a. The booru logger has only the in-app
QTextEdit LogHandler attached (main_window.py:436-440), so the
POPOUT_FSM dispatch trace from the state machine adapter only
reaches the Ctrl+L log panel — invisible from the shell.

Adds a stderr StreamHandler attached directly to the
`booru.popout.adapter` logger so:

  python -m booru_viewer.main_gui 2>&1 | grep POPOUT_FSM

works during the commit-14a verification gate. The user can capture
the dispatch trace per scenario and compare it to the legacy path's
actions before commit 14b switches authority.

The handler is tagged with a `_is_popout_fsm_stderr` sentinel
attribute so re-imports of window.py don't stack duplicate
handlers (defensive — module-level code only runs once per process,
but the check costs nothing).

Format: `[HH:MM:SS.mmm] POPOUT_FSM <event> | <old> -> <new> | effects=[...]`
The millisecond precision matters for the seek scenario where the
race window is sub-100ms.

Propagation to the parent booru logger is left enabled, so dispatch
trace lines also continue to land in the in-app log panel for the
user who prefers Ctrl+L.

Tests still pass (81 / 81). No behavior change to widgets — this
only affects log output routing.
This commit is contained in:
pax 2026-04-08 20:01:16 -05:00
parent 45e6042ebb
commit 35d80c32f2

View File

@ -45,7 +45,31 @@ from .viewport import Viewport, _DRIFT_TOLERANCE
# dispatch call logs at DEBUG with the event name, state transition, # dispatch call logs at DEBUG with the event name, state transition,
# and effect list. The user filters by `POPOUT_FSM` substring to see # and effect list. The user filters by `POPOUT_FSM` substring to see
# only the state machine activity during the manual sweep. # only the state machine activity during the manual sweep.
#
# The booru logger (parent) only has the in-app QTextEdit LogHandler
# attached (see main_window.py:436-440), so propagating to it routes
# the dispatch trace to the Ctrl+L log panel — useful but invisible
# from the shell. We additionally attach a stderr StreamHandler to
# the adapter logger so `python -m booru_viewer.main_gui 2>&1 |
# grep POPOUT_FSM` works during the commit-14a verification gate.
# The handler is tagged with a sentinel attribute so re-imports
# don't stack duplicates.
import sys as _sys
_fsm_log = logging.getLogger("booru.popout.adapter") _fsm_log = logging.getLogger("booru.popout.adapter")
_fsm_log.setLevel(logging.DEBUG)
if not any(
getattr(h, "_is_popout_fsm_stderr", False) for h in _fsm_log.handlers
):
_stderr_handler = logging.StreamHandler(_sys.stderr)
_stderr_handler.setLevel(logging.DEBUG)
_stderr_handler.setFormatter(
logging.Formatter(
"[%(asctime)s.%(msecs)03d] %(message)s",
datefmt="%H:%M:%S",
)
)
_stderr_handler._is_popout_fsm_stderr = True
_fsm_log.addHandler(_stderr_handler)
## Overlay styling for the popout's translucent toolbar / controls bar ## Overlay styling for the popout's translucent toolbar / controls bar