spacr.logging_util

Package-scope Python logging setup for spacr.

Central configuration for every spacr subsystem — core pipelines, I/O, measure, utilities, and the Qt GUI all funnel through the same rotating file handler at ~/.spacr/logs/spacr.log.

Two ways to opt in:

  • Automatic — the Qt GUI calls setup_logging() at launch, so once you run spacr-qt the log file is populated for the life of the session.

  • Manual — for headless scripts and notebooks:

    from spacr.logging_util import setup_logging, get_logger
    setup_logging()                # once, at program start
    LOG = get_logger(__name__)     # in every module that logs
    LOG.info("started")
    

The log level can be overridden by SPACR_LOG_LEVEL in the env (DEBUG, INFO, WARNING, …). enable_debug() and disable_debug() are convenience toggles for interactive use.

Third-party libraries that spam INFO records during a spacr pipeline (torch, cellpose, matplotlib, PIL, urllib3, botocore, tensorflow, asyncio) are pinned to WARNING so the log stays useful. Add more to QUIET_LOGGERS if a new dependency starts spamming.

Public API:

setup_logging(level=INFO, log_file=None) — call once early. get_logger(name) — module-scoped logger. enable_debug() — crank spacr.* to DEBUG. disable_debug() — revert to session level. log_dir() — folder holding the log. log_path() — absolute log file path.

Classes

LevelSetFilter

Pass only records whose level is in an explicitly enabled set.

Timer

Context manager that logs the elapsed wall-clock for a block.

Functions

apply_level_policy() → tuple)

Re-gate the live handlers from the Preferences switches.

clamp_console_to_file(→ frozenset)

Console levels are a subset of what the file records.

disable_debug(→ None)

Revert every spacr.* logger to the level chosen at setup.

disable_function_trace(→ None)

Remove spaCR's function trace and restore prior profile hooks.

disable_timing(→ None)

Turn off all timing logs — the decorators become pass-through.

enable_debug(→ None)

Crank every spacr.* logger to DEBUG.

enable_function_trace(→ None)

Trace every spaCR Python function and method at DEBUG level.

enable_timing(→ None)

Turn on the @timed decorator + Timer context manager.

function_trace_enabled(→ bool)

Return whether spaCR function-level DEBUG tracing is active.

get_logger(→ logging.Logger)

Return a spacr-scoped logging.Logger.

log_dir(→ pathlib.Path)

Return the folder where spacr log files live.

log_path(→ pathlib.Path)

Return the absolute path of the rotating log file.

normalise_levels(→ frozenset)

Keep only the five switchable levels, discarding anything else.

set_timing_threshold_ms(→ None)

Only log timings that exceed ms milliseconds.

setup_logging(→ pathlib.Path)

Install the rotating file handler on the root logger.

time_module() → int)

Wrap every public function in module with timed().

timed(→ TimingCallable)

Decorator that logs wall-clock time for every call to fn.

Module Contents

class spacr.logging_util.LevelSetFilter(levels: Iterable[int] = ())[source]

Bases: logging.Filter

Pass only records whose level is in an explicitly enabled set.

setLevel is a threshold: enabling DEBUG necessarily enables everything above it. The preference this serves is a set of independent switches, where DEBUG on with INFO off is a legitimate choice, so the gate has to be membership rather than comparison.

The set is mutable in place so a live handler can be re-gated from the Preferences dialog without being torn down and rebuilt, which would race against any thread logging at that moment.

Parameters:

levels – the logging level numbers to pass. Empty – the default – passes NOTHING, which is deliberate: a filter that let everything through until configured would leak records during the window before Preferences applies its choice.

Create an independently switchable logging-level filter.

Parameters:

levels – exact numeric levels to pass; an empty iterable passes no records.

filter(record: logging.LogRecord) → bool[source]

Return whether record has one of the enabled exact levels.

Parameters:

record – logging record presented by a handler.

Returns:

True only when record.levelno is enabled.

class spacr.logging_util.Timer(label: str, logger: str = 'spacr.timing', level: int = logging.INFO)[source]

Context manager that logs the elapsed wall-clock for a block.

Example:

from spacr.logging_util import Timer

with Timer("preprocess field"):
    do_work()
# -> logs "preprocess field took 12.3 ms"

Nested Timers work fine — each logs its own block independently.

Parameters:
  • label – human-readable name shown in the log line.

  • logger – name of the logger to write to (default "spacr.timing").

  • level – logging level for the line (default INFO).

Variables:

elapsed_ms – filled on exit; None while still running.

Create an idle timer with its logging destination.

Parameters:
  • label – human-readable operation name included in timing records.

  • logger – logger name that receives completed timings.

  • level – logging level used for completed timings.

__enter__() → Timer[source]

Start or restart timing and clear any previous duration.

Returns:

this timer, with elapsed_ms unset while it runs.

__exit__(exc_type, exc, tb) → None[source]

Finish one active interval without suppressing body exceptions.

Parameters:
  • exc_type – exception type raised by the block, when present.

  • exc – exception instance raised by the block, when present.

  • tb – traceback raised by the block, when present.

Returns:

None so any body exception continues to propagate.

spacr.logging_util.apply_level_policy(file_levels: Iterable[int], console_levels: Iterable[int] = ()) → tuple[source]

Re-gate the live handlers from the Preferences switches.

Parameters:
  • file_levels – levels written to the log files.

  • console_levels – levels echoed to the in-app console; silently clamped to a subset of file_levels.

Returns:

(file_levels, console_levels) as actually applied.

spacr.logging_util.clamp_console_to_file(console: Iterable[int], file_levels: Iterable[int]) → frozenset[source]

Console levels are a subset of what the file records.

A level the log file discards cannot reach the console, because the console is fed from the same records. Showing a user a line they will not find in the log file they are about to attach to a bug report is worse than not showing it.

Parameters:
  • console – numeric logging levels requested for the console.

  • file_levels – numeric logging levels the log file records; only levels in both sets are kept.

spacr.logging_util.disable_debug() → None[source]

Revert every spacr.* logger to the level chosen at setup.

Inverse of enable_debug().

spacr.logging_util.disable_function_trace() → None[source]

Remove spaCR’s function trace and restore prior profile hooks.

spacr.logging_util.disable_timing() → None[source]

Turn off all timing logs — the decorators become pass-through.

spacr.logging_util.enable_debug() → None[source]

Crank every spacr.* logger to DEBUG.

Useful when debugging interactively:

>>> from spacr.logging_util import enable_debug
>>> enable_debug()

Third-party loggers listed in QUIET_LOGGERS are left at WARNING to keep the log readable.

spacr.logging_util.enable_function_trace() → None[source]

Trace every spaCR Python function and method at DEBUG level.

The hook is installed for the calling thread, all future Python threads, and—on Python 3.12+—threads that already exist. Calls outside the spaCR package are ignored. Repeated calls are idempotent.

This is intentionally verbose and has measurable overhead. With it installed, the Home screen took 7.4 s to become usable, against 4.0-4.2 s without it. Nothing in the GUI installs it, and the Verbose logging preference does not either. Normal operation has no profile hook installed.

spacr.logging_util.enable_timing() → None[source]

Turn on the @timed decorator + Timer context manager.

Enabled by default. Call this after disable_timing() to re-enable timing logs at runtime.

spacr.logging_util.function_trace_enabled() → bool[source]

Return whether spaCR function-level DEBUG tracing is active.

The trace is controlled by enable_function_trace() and disable_function_trace(). The GUI’s Verbose logging preference does not install it. It never records arguments or return values, which avoids copying large arrays and keeps API keys or filesystem metadata out of the diagnostic log.

spacr.logging_util.get_logger(name: str) → logging.Logger[source]

Return a spacr-scoped logging.Logger.

Idiomatic usage from any module:

from spacr.logging_util import get_logger
LOG = get_logger(__name__)
Parameters:

name – logger name — typically __name__ so the log stream shows which module the record came from.

spacr.logging_util.log_dir() → pathlib.Path[source]

Return the folder where spacr log files live.

SPACR_LOG_DIR overrides the default, which is useful for portable installs, read-only home directories and test/embedding hosts.

Returns:

the configured directory, otherwise ~/.spacr/logs.

spacr.logging_util.log_path() → pathlib.Path[source]

Return the absolute path of the rotating log file.

Uses whatever was passed to setup_logging() last, or the default under log_dir() when never set.

spacr.logging_util.normalise_levels(levels: Iterable[int]) → frozenset[source]

Keep only the five switchable levels, discarding anything else.

Parameters:

levels – numeric logging levels (anything int() accepts); values other than DEBUG, INFO, WARNING, ERROR and CRITICAL are dropped.

spacr.logging_util.set_timing_threshold_ms(ms: int) → None[source]

Only log timings that exceed ms milliseconds.

Defaults to 5 ms (env-overridable via SPACR_TIME_THRESHOLD_MS). Setting to 0 logs every call.

Parameters:

ms – threshold in milliseconds, converted with int(); negative values are clamped to 0.

spacr.logging_util.setup_logging(level: int | None = None, log_file: pathlib.Path | None = None, stream: bool = False, quiet: Iterable[str] = QUIET_LOGGERS) → pathlib.Path[source]

Install the rotating file handler on the root logger.

Idempotent — subsequent calls only re-apply the level, they don’t stack additional handlers. Honours the SPACR_LOG_LEVEL environment variable when level is not given.

Parameters:
  • level – minimum record level for the log file. Defaults to SPACR_LOG_LEVEL env var (any of DEBUG/INFO/…) or logging.INFO.

  • log_file – override for where the file lands. Defaults to log_path().

  • stream – also attach a StreamHandler to stderr — handy for headless / CI runs where the log file isn’t inspected.

  • quiet – iterable of logger names to pin at WARNING. Defaults to QUIET_LOGGERS.

Returns:

the resolved log-file path.

spacr.logging_util.time_module(module, exclude: tuple = ()) → int[source]

Wrap every public function in module with timed().

Idempotent — functions that already carry __spacr_timed__ are skipped. Handy during ad-hoc profiling; not recommended for production imports because it slows every call by ~1 µs.

Example:

import spacr.core
from spacr.logging_util import time_module
time_module(spacr.core)
# Every public function on spacr.core now logs its timing.
Parameters:
  • module – the module object to wrap.

  • exclude – iterable of function names to skip.

Returns:

how many functions were wrapped.

spacr.logging_util.timed(fn: TimingCallable | None = None, *, name: str | None = None, level: int = logging.INFO) → TimingCallable[source]

Decorator that logs wall-clock time for every call to fn.

Usable as @timed or @timed(name="…", level=DEBUG):

@timed
def preprocess_generate_masks(settings):
    ...

@timed(name="cellpose.batch", level=logging.DEBUG)
def _run_batch(...):
    ...

Log line format: func_name took 1234.5 ms. The logger name is spacr.timing unless the wrapped function’s module starts with spacr., in which case that module’s logger is reused so timings interleave with the function’s own log records.

Parameters:
  • fn – the function to wrap (when used as @timed).

  • name – override the label used in the log line.

  • level – logging level for the “took Xms” line.

Nested helpers

timed._decorate(inner: TimingCallable) → TimingCallable

Wrap inner with the selected label, logger, and timing marks.

spacr/logging_util.py:1041

timed._decorate._wrapped(*args, **kwargs)

Call inner and log qualifying elapsed time, even on error.

spacr/logging_util.py:1048