exlab_wizard.logging.manager#

Canonical logger factory + configuration. Backend Spec §16.2.1, §16.2.2, §16.2.5.

This module is the single allowed call site for logging.getLogger in the codebase (§16.2.1; pre-commit lint enforces this). All component authors import get_logger() from exlab_wizard.logging.

The on-disk handler chain is wired up by configure_logging(), which is invoked once during the FastAPI lifespan startup (§4.5) and may be re-invoked after a PUT /api/v1/config to pick up a new logging.level or rotation policy.

The architecture follows §16.2.5: every logger.info(...) call enqueues the record on a queue.Queue via logging.handlers.QueueHandler, and a dedicated background thread (QueueListener) drains the queue into the actual handlers (per-equipment file, central rotating file, and the stderr stream). This keeps the asyncio event loop unblocked on log calls while preserving stdlib compatibility for any third-party library that does logging.getLogger(__name__).info(...).

Functions

configure_logging([config])

Install the §16.2.5 queue-based handler chain.

get_logger(name)

Return a logger for name.

exlab_wizard.logging.manager.configure_logging(config=None)[source]#

Install the §16.2.5 queue-based handler chain.

Idempotent: calling this a second time tears down the existing listener and rebuilds it with the new config. In-flight log records that have already been enqueued are drained into the old handlers before they’re replaced (see QueueListener.stop), so a PUT /api/v1/config reconfigure does not lose log output.

On first call:

  1. Sets the root logger’s level threshold from config.level (default INFO if config is None).

  2. Creates a fresh unbounded queue.Queue.

  3. Wires a QueueHandler onto the root logger so every logger.info(...) returns immediately.

  4. Builds the real handlers (per-equipment file, central rotating file, stderr stream) and starts a QueueListener thread that drains the queue into them.

The per-equipment file handler is only installed when a local_root is configured (Phase 3A’s configure_logging accepts a LoggingConfig only; future phases may pass an explicit local_root argument). When local_root is unset, the chain falls back to central + stderr only – this keeps unit tests that don’t model an equipment root from crashing.

Parameters:

config (LoggingConfig | None)

Return type:

None

exlab_wizard.logging.manager.get_logger(name)[source]#

Return a logger for name.

The ONLY place in the codebase that may call logging.getLogger(). Component authors must use this entry point; a pre-commit lint rule rejects direct logging.getLogger calls in any module under exlab_wizard/ other than this one (§16.2.1).

The returned logger inherits the root level set by configure_logging(). If configure_logging has not been called yet (e.g. during early module import or in unit tests that don’t exercise the handler chain), the logger still works – it just falls through to the stdlib root logger’s defaults until the first configure_logging call.

Parameters:

name (str)

Return type:

Logger