diff --git a/docs/database.md b/docs/database.md index dc4ccd0..e4e73a3 100644 --- a/docs/database.md +++ b/docs/database.md @@ -210,7 +210,7 @@ def resolve_user_key(value: str) -> str | None: - By default a transaction is logged with an `action by user` header followed by the diff lines. Added paths are colored green, deleted paths red. ANSI color codes are stripped after formatting when the standard error stream does not support color: `NO_COLOR` disables colors, `FORCE_COLOR` forces them, otherwise a tty check and a journald (`JOURNAL_STREAM`) check decide. The CLI (`python -m kanta`) strips its output the same way. - `kanta.transaction(..., extra=...)` accepts a display-only value that is shown after the action in the header. Anything other than `None` is printed str-converted (colored by Kanta), unless a custom logemit handler does something else with it; it is never persisted in the `ChangeRecord`. - `kanta.transaction(..., logdiff=False)` skips building and printing the diff body and logs only the header, which is useful for large or noisy changesets. Diff output can also be disabled globally with `configure_logging(diff=False)`; diff lines are emitted on the `kanta.transaction.diff` child logger so applications can route or silence them separately from the headers. -- The event loggers `kanta.bootstrap`, `kanta.migration` and `kanta.transaction` are configured at import time (via `configure_logging()`, callable again to change the toggles): a plain stderr handler with no prefix and `propagate = False`, since Kanta renders this output itself. No levels are set, so they inherit the effective root level — a framework switching root between INFO in development and WARNING in production governs Kanta output too. Operational diagnostics (integrity errors, flush failures, rotation notes) use the plain `kanta` logger instead, propagating to the root logger and following the application's normal logging configuration. +- The event loggers `kanta.bootstrap`, `kanta.migration` and `kanta.transaction` are configured at import time (via `configure_logging()`, callable again to change the toggles): a plain stderr handler with no prefix and `propagate = False`, since Kanta renders this output itself. They are set to INFO so Kanta output is visible without any application logging setup (`debug=True` lifts the DEBUG-emitting bootstrap/migration loggers to DEBUG); only loggers Kanta owns are touched. Operational diagnostics (integrity errors, flush failures, rotation notes) use the plain `kanta` logger instead, which is left unset — propagating to the root logger and following the application's normal logging configuration. #### Log Emitters diff --git a/kanta/logging.py b/kanta/logging.py index 99d31ee..152a06f 100644 --- a/kanta/logging.py +++ b/kanta/logging.py @@ -605,10 +605,11 @@ def configure_logging( and ``kanta.transaction`` carry Kanta-rendered output (colored headers, diffs) and print it bare through a plain stderr handler with ``propagate = False``. Diagnostic messages use the plain ``kanta`` - logger and follow the application's root logging configuration. + logger, which is left untouched so the application's root logging + configuration controls its verbosity. - No levels are set by default: the event loggers inherit the effective - level of the root logger. + The event loggers are set to ``INFO`` so Kanta output is visible without + any application logging setup; only loggers Kanta owns are touched. Args: skiproot: If ``True`` (default), event loggers print through Kanta's @@ -625,7 +626,7 @@ def configure_logging( debug: Whether to set the event loggers that emit DEBUG-level output (bootstrap and migration) to ``DEBUG``, revealing output such as the file-opened summary and migration diffs. ``False`` resets - them to inheriting the root level. + all event loggers to ``INFO``. """ logging.getLogger("kanta.transaction.diff").disabled = not diff @@ -636,11 +637,11 @@ def configure_logging( ): logging.getLogger(name).disabled = not enabled - for name in _DEBUG_LOGGERS: - logging.getLogger(name).setLevel(logging.DEBUG if debug else logging.NOTSET) - for name in EVENT_LOGGERS: logger = logging.getLogger(name) + logger.setLevel( + logging.DEBUG if debug and name in _DEBUG_LOGGERS else logging.INFO + ) if skiproot: logger.propagate = False _ensure_plain_handler(logger) diff --git a/tests/test_kanta_integration.py b/tests/test_kanta_integration.py index 563ec39..a24eef0 100644 --- a/tests/test_kanta_integration.py +++ b/tests/test_kanta_integration.py @@ -769,7 +769,11 @@ async def test_transaction_log_false_suppresses_log(tmp_path, format_config, cap await kanta.close() - info_messages = [r for r in caplog.records if r.levelno == logging.INFO] + info_messages = [ + r + for r in caplog.records + if r.levelno == logging.INFO and r.name.startswith("kanta.transaction") + ] assert not info_messages @@ -787,7 +791,11 @@ async def test_transaction_logdiff_false_logs_header_only( await kanta.close() - messages = [r.message for r in caplog.records if r.levelno == logging.INFO] + messages = [ + r.message + for r in caplog.records + if r.levelno == logging.INFO and r.name.startswith("kanta.transaction") + ] assert len(messages) == 1 assert "inc" in messages[0] assert "counter" not in messages[0] @@ -808,7 +816,11 @@ async def test_transaction_log_custom_logger(tmp_path, format_config, caplog): await kanta.close() - info_messages = [r for r in caplog.records if r.levelno == logging.INFO] + info_messages = [ + r + for r in caplog.records + if r.levelno == logging.INFO and r.name.startswith("custom.transaction") + ] assert len(info_messages) >= 1 assert "inc" in info_messages[0].message diff --git a/tests/test_logging.py b/tests/test_logging.py index 13b2e18..1a73dcb 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -42,11 +42,18 @@ def test_configure_logging_defaults(): configure_logging() for name in ("kanta.bootstrap", "kanta.migration", "kanta.transaction"): logger = logging.getLogger(name) - assert logger.level == logging.NOTSET # inherits the root level + assert logger.level == logging.INFO # visible without app logging setup assert not logger.propagate assert logger.handlers +def test_configure_logging_debug_lifts_debug_loggers(): + configure_logging(debug=True) + assert logging.getLogger("kanta.bootstrap").level == logging.DEBUG + assert logging.getLogger("kanta.migration").level == logging.DEBUG + assert logging.getLogger("kanta.transaction").level == logging.INFO + + def test_configure_logging_disables_specific_loggers(): configure_logging(bootstrap=False, migration=False, transaction=False) assert logging.getLogger("kanta.bootstrap").disabled @@ -63,14 +70,8 @@ def test_configure_logging_skiproot_false_routes_via_root(): def _setup_logging(**kwargs): - """Default kanta logging with the event loggers lifted to INFO. - - Event loggers inherit the root level (WARNING under pytest); output - assertions need INFO. - """ + """Default kanta logging; the event loggers are INFO out of the box.""" configure_logging(**kwargs) - for name in ("kanta.bootstrap", "kanta.migration", "kanta.transaction"): - logging.getLogger(name).setLevel(logging.INFO) def test_log_change_no_diff(capsys): @@ -114,7 +115,6 @@ def test_configure_logging_diff_false(capsys): def test_configure_logging_diff_true_reenables(capsys): _setup_logging(diff=False) configure_logging(diff=True) - logging.getLogger("kanta.transaction").setLevel(logging.INFO) log_change("update", {"counter": 5}, previous={}) captured = capsys.readouterr() assert "counter" in captured.err