From a41f34d33267a5b1c9f15447eb5fcf71759cdf4c Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 16 Sep 2026 02:16:58 +0000 Subject: [PATCH] Route all diagnostic logging through the plain "kanta" logger. Module loggers used __name__, splitting diagnostics across eight module-named loggers and colliding kanta.transaction with the transaction event channel. Diagnostics (integrity errors, flush failures, rotation notes) are few; they now all go through the "kanta" logger, following the application's root logging configuration like any ordinary library output. --- kanta/__main__.py | 16 ++++++---------- kanta/callbacks.py | 34 ++++++++++++++++++++++++++++++++-- kanta/filelock.py | 2 +- kanta/kantaimpl.py | 11 +++-------- kanta/persistence.py | 14 +++----------- kanta/rotation.py | 2 +- kanta/snapshot.py | 4 ++-- kanta/transaction.py | 2 +- 8 files changed, 49 insertions(+), 36 deletions(-) diff --git a/kanta/__main__.py b/kanta/__main__.py index 9ddffa0..0939f16 100644 --- a/kanta/__main__.py +++ b/kanta/__main__.py @@ -17,7 +17,7 @@ from typing import Any import msgspec from kanta import Kanta -from kanta.callbacks import InjectionContext +from kanta.callbacks import InjectionContext, callback_error_reporter from kanta.exceptions import DatabaseError, DataIntegrityError, ReplayError from kanta.grep import GrepPattern, evaluate from kanta.logging import ( @@ -50,8 +50,6 @@ EXIT_PARSE_ERROR = 10 EXIT_MIGRATION_ERROR = 20 EXIT_VALIDATION_ERROR = 21 -_logger = logging.getLogger(__name__) - def _print(*args: Any) -> None: """Print to stderr, stripping ANSI codes when the stream has no color support. @@ -408,13 +406,11 @@ async def _log_migration( """ registry = kanta._impl.callback_registry if registry.has("logmigr"): - try: - await registry.invoke( - "logmigr", - InjectionContext(kanta=kanta, report=result), - ) - except Exception: - _logger.exception("logmigr callback failed") + await registry.invoke( + "logmigr", + InjectionContext(kanta=kanta, report=result), + on_error=callback_error_reporter("logmigr"), + ) return if quiet: return diff --git a/kanta/callbacks.py b/kanta/callbacks.py index 0663165..2c66b65 100644 --- a/kanta/callbacks.py +++ b/kanta/callbacks.py @@ -54,7 +54,37 @@ def _state_tag(ann: Any) -> str | None: return None -_logger = logging.getLogger(__name__) +_logger = logging.getLogger("kanta") + + +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)``. + """ + name = getattr(callback, "__name__", None) or type(callback).__name__ + doc = inspect.getdoc(callback) + if doc: + return f"{name} ({doc.splitlines()[0]})" + return name + + +def callback_error_reporter( + kind: str, +) -> 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. + """ + + def _report(callback_error: Exception, callback: Callable[..., Any]) -> None: + _logger.exception( + "Kanta.%s %s failed: %s", kind, describe_callback(callback), callback_error + ) + + return _report class LogFmt: @@ -281,7 +311,7 @@ class CallbackRegistry: except Exception: # Formatting must never break functionality; a failing # callback is reported and treated as a fall-through. - _logger.exception("logfmt callback %r failed", fn) + _logger.exception("Kanta.logfmt %s failed", describe_callback(fn)) continue if resolved is not None: return resolved diff --git a/kanta/filelock.py b/kanta/filelock.py index 3166538..679ed06 100644 --- a/kanta/filelock.py +++ b/kanta/filelock.py @@ -16,7 +16,7 @@ from pathlib import Path from kanta.exceptions import FileLockError -_logger = logging.getLogger(__name__) +_logger = logging.getLogger("kanta") def _fatal(msg: str, *, db_path: Path | None = None) -> None: diff --git a/kanta/kantaimpl.py b/kanta/kantaimpl.py index 089a9ba..acf7f21 100644 --- a/kanta/kantaimpl.py +++ b/kanta/kantaimpl.py @@ -10,7 +10,7 @@ from datetime import UTC, datetime, timedelta from types import SimpleNamespace from typing import Any, Generic, TypeVar -from kanta.callbacks import CallbackRegistry, InjectionContext +from kanta.callbacks import CallbackRegistry, InjectionContext, callback_error_reporter from kanta.exceptions import DatabaseError, DataIntegrityError, ReplayError from kanta.logging import ( _USER_PATH, @@ -25,16 +25,11 @@ from kanta.rotation import execute_rotation, plan_rotation from kanta.serialization import restore_data_in_place, struct_to_dict from kanta.serialization.base import replay -_logger = logging.getLogger(__name__) +_logger = logging.getLogger("kanta") T = TypeVar("T") -def _log_callback_error(callback_error, callback): - """Report a failing logging callback and continue with the next one.""" - _logger.exception("Log callback %r failed: %s", callback, callback_error) - - class KantaImpl(PersistenceMixin, Generic[T]): """Internal state and logic for Kanta.""" @@ -119,7 +114,7 @@ class KantaImpl(PersistenceMixin, Generic[T]): kanta=self._kanta, report=report, ), - on_error=_log_callback_error, + on_error=callback_error_reporter("logmigr"), ) return diff --git a/kanta/persistence.py b/kanta/persistence.py index 196314b..5eef29c 100644 --- a/kanta/persistence.py +++ b/kanta/persistence.py @@ -12,7 +12,7 @@ from datetime import UTC, datetime from pathlib import Path from typing import Any -from kanta.callbacks import CallbackRegistry, InjectionContext +from kanta.callbacks import CallbackRegistry, InjectionContext, callback_error_reporter from kanta.diff import diff from kanta.exceptions import DatabaseError, DataIntegrityError from kanta.filelock import LockedFile @@ -21,7 +21,7 @@ from kanta.serialization import JsonSerializer, Serializer from kanta.serialization.framing import Framer from kanta.snapshot import SnapshotState -_logger = logging.getLogger(__name__) +_logger = logging.getLogger("kanta") class PersistenceMixin: @@ -110,18 +110,10 @@ class PersistenceMixin: break except DatabaseError as e: self.background_error = e - - def _log_callback_error(callback_error, callback): - _logger.exception( - "Background error callback %r failed: %s", - callback, - callback_error, - ) - await self.callback_registry.invoke( "fatal_error", InjectionContext(error=e, kanta=self._kanta), - on_error=_log_callback_error, + on_error=callback_error_reporter("fatal_error"), ) _logger.error("Background flush loop stopped: %s", e) break diff --git a/kanta/rotation.py b/kanta/rotation.py index 41d124a..55a342b 100644 --- a/kanta/rotation.py +++ b/kanta/rotation.py @@ -20,7 +20,7 @@ from kanta.structs import ChangeRecord, Snapshot from kanta.serialization.base import Serializer, apply_diff from kanta.serialization.framing import Framer -_logger = logging.getLogger(__name__) +_logger = logging.getLogger("kanta") @dataclass diff --git a/kanta/snapshot.py b/kanta/snapshot.py index dd92ebe..c13f337 100644 --- a/kanta/snapshot.py +++ b/kanta/snapshot.py @@ -10,7 +10,7 @@ from kanta.structs import Snapshot from kanta.serialization import JsonSerializer, Serializer from kanta.serialization.framing import Framer, LineFramer -_logger = logging.getLogger(__name__) +_logger = logging.getLogger("kanta") MINDIFFS = 100 @@ -69,7 +69,7 @@ class SnapshotState: self._write(file, version, state, ts, m=m) self._force_pending = False except Exception as exc: - _logger.error("snapshot: failed to write snapshot: %r", exc) + _logger.error("Kanta snapshot failed: %r", exc) def _write( self, file, version: int, state: dict, now: datetime, m: datetime | None = None diff --git a/kanta/transaction.py b/kanta/transaction.py index d69b410..29608a2 100644 --- a/kanta/transaction.py +++ b/kanta/transaction.py @@ -13,7 +13,7 @@ from kanta.callbacks import InjectionContext from kanta.logging import _USER_PATH, LogEvent, emit_event, transaction_logger from kanta.serialization import restore_data_in_place, struct_to_dict -_logger = logging.getLogger(__name__) +_logger = logging.getLogger("kanta") def _build_logfmt(impl, previous: dict, current: dict):