spacr.qt.verbose_logger

Verbose diagnostic logger for the Qt GUI.

When the “Verbose logging” preference is on, spaCR’s Python loggers are dialled up to DEBUG and the log files keep those records. The active ConsolePanel shows only the levels switched on for the console on the Logging tab of Preferences. That puts a detailed trail on disk, which is what you want when triaging a bug report.

The handler is a lazy module-level singleton so multiple apply_verbose_logging() calls (e.g. every time the Preferences dialog is saved) don’t stack up handlers or leak references. Turning verbose off leaves the handler attached but silent — cheaper than tearing it down and rebuilding it, and safer for cases where a log record fires mid-toggle.

Design:

  • One _ConsoleForwarder handler is added to the root spacr logger. Its emit() hands the formatted line to _ConsoleRelay, which delivers it to whatever ConsolePanel is registered via register_console_target().

  • Registration is a weak reference to avoid keeping a closed screen alive. If the target has been garbage-collected the record is silently dropped.

  • Level and format are set once at first registration; only the verbose gate flips DEBUG ↔ INFO afterwards.

Warning

emit() runs on whatever thread logged the record — Python’s logging module calls handlers inline. A ConsolePanel answers append_stdout by constructing QWidgets (a topic bar and a text block), and Qt forbids building a QWidget anywhere but the GUI thread. Calling the panel straight from emit() therefore built widgets on a worker thread; Qt printed QObject::setParent: Cannot set parent, new parent is in a different thread and then took the process down. It was reproducible from spacr.qt.hf_download._HFDownloadWorker.run(), whose exception path logs a warning from inside the download thread.

_ConsoleRelay is the fix: a QObject pinned to the GUI thread whose line signal is connected to its own bound method, so Qt queues the delivery whenever the emitting thread is not the GUI thread and the panel only ever touches widgets on the thread that owns them.

Functions

apply_console_levels(→ None)

Gate the in-app console to an explicit set of levels.

apply_verbose_logging(→ None)

Flip DEBUG ↔ INFO on every attached spaCR logger + handlers.

console_levels(→ frozenset)

The levels the console is currently showing.

console_write()

Mark the calling thread as being inside a console write.

console_write_in_progress(→ bool)

Whether this thread is currently writing into the console panel.

current_log_file(→ pathlib.Path)

Path of today's rotating log file.

is_verbose(→ bool)

Cheap runtime check — decorated functions call this on entry so

log_button_press(→ None)

Fire a one-line trace record documenting a UI button press.

log_call(→ Callable)

Decorator: log entry + return of fn when verbose mode is on.

log_dir(→ pathlib.Path)

Return ~/.spacr/logs/ — created if it doesn't exist.

register_console_target(→ None)

Point the verbose logger at panel (a ConsolePanel).

Module Contents

spacr.qt.verbose_logger.apply_console_levels(levels) → None[source]

Gate the in-app console to an explicit set of levels.

Called by spacr.logging_util.apply_level_policy(), which has already clamped levels to a subset of what the log files record – a line the user cannot find in the log they are about to attach to a bug report should not appear in the console either.

The handler keeps passing everything and a filter decides, so the set can change while another thread is mid-log without the handler being swapped underneath it.

Parameters:

levels – numeric logging levels the console should show; anything outside DEBUG-CRITICAL is dropped, and attached spaCR loggers are lowered to the lowest level kept.

spacr.qt.verbose_logger.apply_verbose_logging(on: bool) → None[source]

Flip DEBUG ↔ INFO on every attached spaCR logger + handlers.

The user reaches this via the Preferences dialog. It’s idempotent and cheap — safe to call on every dialog save. Also ensures the rotating file handler is attached so bug reports always have a trail on disk regardless of verbose state.

The cellpose logger goes to INFO while verbose is on, so it can say which model it loaded, and back to WARNING when verbose is off.

Parameters:

on – True sets the console handler, file handler and attached spaCR loggers to DEBUG (and cellpose to INFO); False sets them to INFO (and cellpose to WARNING).

spacr.qt.verbose_logger.console_levels() → frozenset[source]

The levels the console is currently showing.

spacr.qt.verbose_logger.console_write()[source]

Mark the calling thread as being inside a console write.

Re-entrant by design: append_error may be reached from inside append_stdout’s own bookkeeping, and unwinding must restore the previous depth rather than clear the latch outright.

spacr.qt.verbose_logger.console_write_in_progress() → bool[source]

Whether this thread is currently writing into the console panel.

Log sinks that feed the console must return without delivering while this is true — see _DELIVERY_STATE.

spacr.qt.verbose_logger.current_log_file() → pathlib.Path[source]

Path of today’s rotating log file.

spacr.qt.verbose_logger.is_verbose() → bool[source]

Cheap runtime check — decorated functions call this on entry so they emit NOTHING when verbose mode is off.

It reads the verbose PREFERENCE as apply_verbose_logging() last applied it, not the console forwarder’s level, which apply_console_levels() holds at DEBUG in every session.

spacr.qt.verbose_logger.log_button_press(button_name: str, context: dict | None = None) → None[source]

Fire a one-line trace record documenting a UI button press.

Wire this from Qt slot handlers so the console shows exactly which button the user hit, with any relevant context values (e.g. the current settings dict on a Run press).

Parameters:

button_name – name of the pressed button, shown in the [button:<name>] trace line; nothing is logged unless verbose mode is on.

spacr.qt.verbose_logger.log_call(fn: Callable) → Callable[source]

Decorator: log entry + return of fn when verbose mode is on.

Zero cost when verbose is off (the wrapper does one attribute check and forwards). When on, emits:

[class.func] args=… kwargs=… [class.func] -> return-repr

Truncates giant reprs to 240 chars so a settings dict with 100 entries doesn’t wreck the console.

Parameters:

fn – the function or method to wrap; its arguments, return value or raised exception are logged to spacr.trace while verbose mode is on.

spacr.qt.verbose_logger.log_dir() → pathlib.Path[source]

Return ~/.spacr/logs/ — created if it doesn’t exist.

Overridable via the SPACR_LOG_DIR env var so tests can point the log at a tmp directory.

spacr.qt.verbose_logger.register_console_target(panel: Any) → None[source]

Point the verbose logger at panel (a ConsolePanel).

The target is stored as a weakref.ref so a closed screen doesn’t keep the panel alive. Any earlier target is replaced.

Called from the GUI thread (the AppScreen constructor), which is where the relay wants to be built — see _ConsoleRelay.

Parameters:

panel – the console panel that receives log lines; it is held by weak reference and dropped when its destroyed signal fires.

Nested helpers

log_call.wrapper(*args, **kwargs)

Call the function, logging it only when verbose is on.

The check is INSIDE rather than at decoration time, so switching verbose on mid-session takes effect without rebuilding anything.

spacr/qt/verbose_logger.py:512