From a4efbca55bc84c257354c1d3dcbd4061a74275ff Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 16 Sep 2026 02:55:10 +0000 Subject: [PATCH] Clarify diagnostic log messages; simplify describe_callback fallback Unnamed callables (partials, callable instances) are described by type name only: docstrings are shown only for named callables, avoiding misleading class docstrings in failure messages. Diagnostic messages revised for clarity when mixed with application logs; logger.exception() messages no longer repeat the exception text, which the traceback already shows. --- kanta/__main__.py | 1 - kanta/callbacks.py | 31 +++++++++++++++++++------------ kanta/logging.py | 11 +++++++---- kanta/persistence.py | 4 +++- kanta/rotation.py | 3 ++- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/kanta/__main__.py b/kanta/__main__.py index 0939f16..d3217c6 100644 --- a/kanta/__main__.py +++ b/kanta/__main__.py @@ -8,7 +8,6 @@ import contextlib import importlib import importlib.metadata import importlib.util -import logging import sys import tempfile from pathlib import Path diff --git a/kanta/callbacks.py b/kanta/callbacks.py index 2c66b65..c4790cd 100644 --- a/kanta/callbacks.py +++ b/kanta/callbacks.py @@ -61,9 +61,13 @@ def describe_callback(callback: Callable[..., Any]) -> str: """Return ``name (docstring first line)`` identifying *callback*. Used in failure messages so a bare log line names the function that - failed, e.g. ``myformatter (Concise log formatter)``. + failed, e.g. ``myformatter (Concise log formatter)``. Callables without + a ``__name__`` (partials, callable instances, ...) are described by + their type name only: less information, but never wrong information. """ - name = getattr(callback, "__name__", None) or type(callback).__name__ + name = getattr(callback, "__name__", None) + if not isinstance(name, str): + return type(callback).__name__ doc = inspect.getdoc(callback) if doc: return f"{name} ({doc.splitlines()[0]})" @@ -75,14 +79,13 @@ def callback_error_reporter( ) -> Callable[[Exception, Callable[..., Any]], None]: """Return an ``on_error`` reporter for :meth:`CallbackRegistry.invoke`. - The returned callable logs ``Kanta. failed: - `` for each failing callback; invoke continues with the rest. + The returned callable logs ``Kanta. failed`` + with the traceback for each failing callback; invoke continues with + the rest. """ def _report(callback_error: Exception, callback: Callable[..., Any]) -> None: - _logger.exception( - "Kanta.%s %s failed: %s", kind, describe_callback(callback), callback_error - ) + _logger.exception("Kanta.%s %s failed", kind, describe_callback(callback)) return _report @@ -275,12 +278,14 @@ class CallbackRegistry: def build_logfmt(self, ctx: InjectionContext) -> Callable[[Any, str], str | None]: """Build a chained formatter from registered logfmt callbacks.""" - formatters: list[tuple[Callable[[Any, str], str | None], str | None]] = [] + formatters: list[ + tuple[Callable[[Any, str], str | None], str | None, Callable[..., Any]] + ] = [] for spec in self._logfmt_callbacks: if isinstance(spec, _LogFmtClassSpec): kwargs = self._build_kwargs(spec.inject_params, ctx) instance: Callable[[Any, str], str | None] = spec.cls(**kwargs) - formatters.append((instance, spec.path)) + formatters.append((instance, spec.path, spec.cls)) else: kwargs = self._build_kwargs(spec.inject_params, ctx) @@ -300,10 +305,10 @@ class CallbackRegistry: return formatter - formatters.append((make_formatter(), spec.path)) + formatters.append((make_formatter(), spec.path, spec.callback)) def format_value(value: Any, path: str) -> str | None: - for fn, pattern in formatters: + for fn, pattern, callback in formatters: if pattern is not None and path != pattern: continue try: @@ -311,7 +316,9 @@ class CallbackRegistry: except Exception: # Formatting must never break functionality; a failing # callback is reported and treated as a fall-through. - _logger.exception("Kanta.logfmt %s failed", describe_callback(fn)) + _logger.exception( + "Kanta.logfmt %s failed", describe_callback(callback) + ) continue if resolved is not None: return resolved diff --git a/kanta/logging.py b/kanta/logging.py index 5a01014..99d31ee 100644 --- a/kanta/logging.py +++ b/kanta/logging.py @@ -17,6 +17,7 @@ from typing import Any import msgspec +from kanta.callbacks import describe_callback from kanta.serialization.base import _apply, unmarshal from kanta.tty import Line, displaywidth, strip_ansi, use_color @@ -158,13 +159,16 @@ def emit_event( try: proceed = handler(ev) except Exception: - _logger.exception("Kanta.logemit callback failed, using default formatting") + _logger.exception( + "Kanta.logemit %s failed, using default formatting", + describe_callback(handler), + ) break if not proceed: return render(ev) except Exception: - _logger.exception("failed to emit %s log event", ev.kind) + _logger.exception("Kanta failed to emit %s log event", ev.kind) def _maybe_strip(text: str) -> str: @@ -604,8 +608,7 @@ def configure_logging( logger and follow the application's root logging configuration. No levels are set by default: the event loggers inherit the effective - level of the root logger, so a framework switching root between INFO in - development and WARNING in production governs Kanta output too. + level of the root logger. Args: skiproot: If ``True`` (default), event loggers print through Kanta's diff --git a/kanta/persistence.py b/kanta/persistence.py index 5eef29c..f5e3399 100644 --- a/kanta/persistence.py +++ b/kanta/persistence.py @@ -115,7 +115,9 @@ class PersistenceMixin: InjectionContext(error=e, kanta=self._kanta), on_error=callback_error_reporter("fatal_error"), ) - _logger.error("Background flush loop stopped: %s", e) + _logger.error( + "Kanta background flush failed; automatic flushing stopped: %s", e + ) break def maybe_snapshot(self) -> None: diff --git a/kanta/rotation.py b/kanta/rotation.py index 55a342b..4ee8319 100644 --- a/kanta/rotation.py +++ b/kanta/rotation.py @@ -216,7 +216,8 @@ def execute_rotation( f.truncate(plan.cutoff_end) if log: _logger.info( - "rotated %s: kept %d change record(s), history before %s moved to %s", + "Rotated database %s: kept %d change record(s), " + "moved history before %s to %s", path, plan.retained_changes, plan.rotated_ts.isoformat(),