logger¶
The logger package provides otto’s logging infrastructure including custom levels and formatters.
otto’s logger accessor.
'otto' is a plain logging.Logger (no subclass). otto modules emit via
get_logger() / logging.getLogger(__name__); child loggers propagate
to 'otto', where the CLI attaches handlers (see otto.logger.management).
Configuring/replacing handlers is up to the application — otto-the-library only
emits (and otto.logger attaches a NullHandler).
-
otto.logger.logger.get_logger(name: str | None =
None) Logger¶ Return the
'otto'logger (or the'otto.<name>'child).
Register short level-name aliases (WARN, CRIT) with the logging system.
Log formatters: multiline splitter and Rich-markup renderer.
- otto.logger.formatters.format_log_time(dt: datetime) Text¶
Format a datetime as a bracketed
[ YYYY-MM-DD HH:MM:SS.mmm ]Rich Text.
-
class otto.logger.formatters.MultilineFormatter(fmt=
None, datefmt=None, style='%', validate=True, *, defaults=None)¶ Bases:
Formatterlogging.Formatterthat formats each line of a multiline message separately.Prevents leading continuation lines from being emitted without the log prefix (timestamp/level), keeping log files and console output parseable.
- format(record: LogRecord) str¶
Format the specified record as text.
The record’s attribute dictionary is used as the operand to a string formatting operation which yields the returned string. Before formatting the dictionary, a couple of preparatory steps are carried out. The message attribute of the record is computed using LogRecord.getMessage(). If the formatting string uses the time (as determined by a call to usesTime(), formatTime() is called to format the event time. If there is exception information, it is formatted using formatException() and appended to the message.
-
class otto.logger.formatters.RichFormatter(fmt: str =
'{asctime} [{levelname:^7}] {message}', style: '%' | '{' ='{', **kwargs: Any)¶ Bases:
MultilineFormatterMultilineFormatterfor the log file handler that controls Rich markup.The console handler uses
rich.logging.RichHandlerdirectly; this formatter is attached to the file handler. WhenrichisTrue, markup is rendered to ANSI escape sequences via an internal capture console; whenrichisFalse(the default), ANSI is stripped so log files stay plain.
Per-command / per-host logging disposition.
LogMode decides where a host’s command I/O is recorded — independent of the
log level (INFO vs DEBUG), which stays native to the logger.info/
logger.debug call. See docs/superpowers/specs/2026-06-28-three-sink-logging-design.md.
- class otto.logger.mode.LogMode(value)¶
Bases:
EnumDisposition of a host’s command echo/output across the log sinks.
NORMAL— logged at the call’s level, shown everywhere.QUIET— suppressed from the console +console.log, kept inverbose.log.NEVER— redacted from every sink at every level, including session diagnostics.
LogModegoverns command I/O only;logger.warning/logger.errorand other non-command records are never suppressed by it.-
NORMAL =
'normal'¶
-
QUIET =
'quiet'¶
-
NEVER =
'never'¶
- otto.logger.mode.effective_mode(*modes: LogMode) LogMode¶
Return the most restrictive of modes (
NORMALwhen called with none).
CLI-side logging management for otto.
otto-the-library only emits log records (via logging.getLogger('otto'…))
and never configures handlers — see otto.logger. This module is the
application/CLI side: it configures the 'otto' logger’s handlers + formatters,
creates each invocation’s output directory, and prunes old log directories.
Context-free by design (it does not import otto.context):
create_output_dir creates and returns the per-run directory; the CLI
records that path on OttoContext.output_dir. These functions are called only
by otto/cli/*.py (and tests).
Scope guardrail: keep this strictly logging config + the per-run dir’s creation/rotation. It is not a catch-all.
- otto.logger.management.verbose_floor(log_level: str) int¶
Return the ‘otto’ logger / verbose.log floor: DEBUG when debugging, else INFO.
- otto.logger.management.set_capture_prefixes(prefixes: Iterable[str]) None¶
Stash the external-logger prefixes to attach once the QueueHandler exists.
Called from the CLI’s main callback (before any subcommand wires the listener via
create_output_dir). The actual attach happens later in_add_log_handlers->capture_external_loggers.
- otto.logger.management.capture_external_loggers(prefixes: Iterable[str]) None¶
Route the named top-level loggers into otto’s sinks (CLI/app only).
Finds the shared
QueueHandleron the'otto'logger and attaches it to each prefix’s logger at the verbose floor, so product code using a plainlogging.getLogger(__name__)is captured without third-party noise. A no-op when the QueueHandler does not exist yet (no output dir wired).
- otto.logger.management.reset() None¶
Reset module state, detach otto’s handlers, and restore library-citizen state — test helper.
Unregisters atexit callbacks before stopping the listener so that real-exit teardown never double-stops a listener or prints a
Noneoutput dir.
-
otto.logger.management.init_cli_logging(xdir: Path, log_level: str, keep_days: float, rich_log_file: bool =
False, show_time: bool =False) None¶ Configure the
'otto'logger for a CLI invocation (was init_otto_logger).
-
otto.logger.management.create_output_dir(command: str, subcommand: str | None =
None) Path¶ Create this invocation’s output dir, wire the file handler, prune old logs, and return it.
The caller records it on
OttoContext.output_dir.