From ca9bf4520bf255b518955fac7a5328781e8e823f Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 6 Jul 2026 00:14:54 -0400 Subject: [PATCH] fix: build_formatter routes the unknown-output warning through the setup buffer build_formatter warned immediately via getLogger(__name__).warning, but setup_logging calls it after _clear_owned strips the lib's handlers and before any new handler attaches - so the warning hit a handler-less root and landed on stderr via lastResort, never reaching the configured log. The v0.6.0 warnings buffer already threads this class of setup-time warning through for an unknown rotate value; build_formatter now takes the same warnings list and appends to it instead of emitting inline, so setup_logging can flush it once handlers are attached like the others. Signed-off-by: disqualifier --- src/log_setup/formats.py | 16 ++++++++++++---- src/log_setup/setup.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/log_setup/formats.py b/src/log_setup/formats.py index 07701cb..88ea57e 100644 --- a/src/log_setup/formats.py +++ b/src/log_setup/formats.py @@ -54,7 +54,7 @@ class JsonLinesFormatter(logging.Formatter): return json.dumps(payload, default=str) -def build_formatter(output: str = "text", fmt=None, datefmt=None) -> logging.Formatter: +def build_formatter(output: str = "text", fmt=None, datefmt=None, warnings: list = None) -> logging.Formatter: """build the formatter for the chosen output format `output="text"` (default) returns the human-readable text formatter, honoring @@ -62,11 +62,19 @@ def build_formatter(output: str = "text", fmt=None, datefmt=None) -> logging.For structured `JsonLinesFormatter` (which ignores `fmt`/`datefmt` - it builds fields, not a format string). an unrecognized `output` falls back to text and warns, never raising - a bad format arg must not take the app down. + + `warnings` is the setup-time buffer (see `setup_logging`): passing it appends + the unknown-output warning there instead of emitting immediately, so it lands + in the configured log like the other setup-time warnings rather than only + stderr. `None` (default) preserves the old immediate-emit behavior for direct + callers outside `setup_logging`. """ if output == "json": return JsonLinesFormatter() if output != "text": - logging.getLogger(__name__).warning( - "log_setup: unknown output %r; falling back to 'text'", output - ) + message = "log_setup: unknown output %r; falling back to 'text'" + if warnings is None: + logging.getLogger(__name__).warning(message, output) + else: + warnings.append((message, output)) return logging.Formatter(fmt or DEFAULT_FORMAT, datefmt or DEFAULT_DATEFMT) diff --git a/src/log_setup/setup.py b/src/log_setup/setup.py index bafefc9..3fa0f25 100644 --- a/src/log_setup/setup.py +++ b/src/log_setup/setup.py @@ -266,7 +266,7 @@ def setup_logging( _apply_module_levels(module_levels, warnings) _clear_owned(root, warnings) - formatter = build_formatter(output, fmt, datefmt) + formatter = build_formatter(output, fmt, datefmt, warnings) stem = _normalize_name(name) live_path = f"{stem}.log" # history_name if given, else the cwd basename; falls back to the live stem for a