exlab_wizard.logging#

Canonical logger package. Backend Spec §16.

This package owns the runtime logger architecture for ExLab-Wizard:

  • get_logger() is the single entry point for logger creation (§16.2.1; the only allowed call site for logging.getLogger).

  • configure_logging() installs the §16.2.5 queue-based handler chain on FastAPI lifespan startup.

  • set_run_context() is a context manager that pushes structured tags (host / equipment / project / run-kind / run-id) onto per-task contextvars so subsequent log calls within the block carry them.

Component authors only need get_logger for normal logging; the launcher and the creation controller use configure_logging and set_run_context respectively. See §16.2 for the full architecture.

exlab_wizard.logging.clear_run_context()[source]#

Reset every context var to None.

Provided for test fixtures that want to ensure a clean slate between cases. Production code should rely on set_run_context()’s exit semantics; calling clear_run_context mid-run would mask a nested with block’s prior values, which is almost always wrong.

Return type:

None

exlab_wizard.logging.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.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

exlab_wizard.logging.get_run_context()[source]#

Return a snapshot of the active context as a plain dict.

Used by the formatter to render structured tags. Keys match the kwarg names of set_run_context(); values are None when the var is unset.

Return type:

dict[str, str | None]

exlab_wizard.logging.set_run_context(*, host=None, equipment_id=None, project_short_id=None, run_kind=None, run_id=None)[source]#

Push the supplied context vars on entry and reset them on exit.

Vars whose argument is None (the default) are left unchanged. This lets a caller incrementally widen the context (e.g. set host and equipment_id in an outer with block, then add run_id in a nested inner block) without destroying the outer values.

The context manager is implemented via contextvars.Token so nested entries are restored to their prior value on exit (not to None). Each token corresponds to exactly one ContextVar.set call.

The caller is responsible for ensuring values are non-empty strings; this helper does no validation. Passing an empty string "" is treated as a real value (it suppresses the default-None suppression in the formatter), which is almost certainly a caller bug – so callers should pass None explicitly when they mean “don’t touch this var”.

Parameters:
Return type:

Iterator[None]

Modules

context

Per-task structured-tag context vars for the logging system.

format

Structured log formatter and secret-redaction helpers.

handlers

Equipment-scoped file handler.

manager

Canonical logger factory + configuration.