scrolling_log_sink
anomaly_match_ui.utils.scrolling_log_sink
¶
Capped, auto-scrolling loguru sink for ipywidgets Output panes.
Four screens (training / training-setup / prediction / prediction-setup)
mirror the loguru stream into an :class:ipywidgets.Output so the user
can watch subprocess INFO lines from the UI. Two problems on long runs
(#430):
Output.append_stdoutretains every line for the lifetime of the widget, so a multi-day run accumulates tens of thousands of entries and slows the kernel / browser.- The Output widget's
overflow="auto"does not auto-scroll — new lines appear below the viewport unless the user scrolls.
:class:ScreenLogSink wraps the sink-creation logic and the
logger.add / logger.remove lifecycle. The sink maintains a
bounded deque of lines and rewrites the widget's outputs
payload in place, so memory stays flat. A one-shot
MutationObserver keeps any widget tagged with the am-log-sink
CSS class pinned to its bottom. The on-disk session.log continues
to hold the unabridged history.
ScreenLogSink(screen_label)
¶
Lifecycle wrapper for a screen-bound, capped, auto-scrolling sink.
Each of the four UI screens (training / training_setup / prediction /
prediction_setup) mirrors the loguru stream into its own Output
widget via the same attach/detach dance: tear down any prior sink,
build a fresh capped sink, logger.add; on screen leave,
logger.remove while tolerating the ValueError that
:meth:Session.set_terminal_out raises after a global sink clear.
This class consolidates that lifecycle so the per-screen wrappers don't have to redefine the same method twice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
screen_label
|
str
|
Human-readable name used in the debug log when a
sink turns out to have already been removed (e.g.
|
required |
Source code in anomaly_match_ui/utils/scrolling_log_sink.py
attach(out)
¶
Bind a capped, auto-scrolling sink to out.
Idempotent — any previously-attached sink is detached first.
Bypasses ipykernel's OutStream (via the underlying
:func:_make_scrolling_log_sink) to dodge parent_header-walk
hangs on thread-ident reuse.
Source code in anomaly_match_ui/utils/scrolling_log_sink.py
detach()
¶
Remove this screen's sink, tolerating session-level clears.
:meth:Session.set_terminal_out may have already removed all
sinks between attach and detach, in which case logger.remove
raises ValueError. Log at DEBUG so genuine bugs don't hide
here.
Source code in anomaly_match_ui/utils/scrolling_log_sink.py
install_auto_scroll()
¶
Inject the global auto-scroll observer into the front-end.
Call once from a notebook display context (e.g. the app's show()
method). Subsequent calls are harmless: the JS itself is idempotent
via window.__amLogScrollObserverInstalled. Calling from inside
a sink (i.e. from a loguru dispatcher thread) does not publish to
the front-end, which is why this is a separate entry point.