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.
This commit is contained in:
2026-09-16 02:16:58 +00:00
parent 7a7716ec7b
commit a41f34d332
8 changed files with 49 additions and 36 deletions
+2 -6
View File
@@ -17,7 +17,7 @@ from typing import Any
import msgspec import msgspec
from kanta import Kanta 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.exceptions import DatabaseError, DataIntegrityError, ReplayError
from kanta.grep import GrepPattern, evaluate from kanta.grep import GrepPattern, evaluate
from kanta.logging import ( from kanta.logging import (
@@ -50,8 +50,6 @@ EXIT_PARSE_ERROR = 10
EXIT_MIGRATION_ERROR = 20 EXIT_MIGRATION_ERROR = 20
EXIT_VALIDATION_ERROR = 21 EXIT_VALIDATION_ERROR = 21
_logger = logging.getLogger(__name__)
def _print(*args: Any) -> None: def _print(*args: Any) -> None:
"""Print to stderr, stripping ANSI codes when the stream has no color support. """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 registry = kanta._impl.callback_registry
if registry.has("logmigr"): if registry.has("logmigr"):
try:
await registry.invoke( await registry.invoke(
"logmigr", "logmigr",
InjectionContext(kanta=kanta, report=result), InjectionContext(kanta=kanta, report=result),
on_error=callback_error_reporter("logmigr"),
) )
except Exception:
_logger.exception("logmigr callback failed")
return return
if quiet: if quiet:
return return
+32 -2
View File
@@ -54,7 +54,37 @@ def _state_tag(ann: Any) -> str | None:
return 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.<kind> <name (docstring)> failed:
<error>`` 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: class LogFmt:
@@ -281,7 +311,7 @@ class CallbackRegistry:
except Exception: except Exception:
# Formatting must never break functionality; a failing # Formatting must never break functionality; a failing
# callback is reported and treated as a fall-through. # 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 continue
if resolved is not None: if resolved is not None:
return resolved return resolved
+1 -1
View File
@@ -16,7 +16,7 @@ from pathlib import Path
from kanta.exceptions import FileLockError from kanta.exceptions import FileLockError
_logger = logging.getLogger(__name__) _logger = logging.getLogger("kanta")
def _fatal(msg: str, *, db_path: Path | None = None) -> None: def _fatal(msg: str, *, db_path: Path | None = None) -> None:
+3 -8
View File
@@ -10,7 +10,7 @@ from datetime import UTC, datetime, timedelta
from types import SimpleNamespace from types import SimpleNamespace
from typing import Any, Generic, TypeVar 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.exceptions import DatabaseError, DataIntegrityError, ReplayError
from kanta.logging import ( from kanta.logging import (
_USER_PATH, _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 import restore_data_in_place, struct_to_dict
from kanta.serialization.base import replay from kanta.serialization.base import replay
_logger = logging.getLogger(__name__) _logger = logging.getLogger("kanta")
T = TypeVar("T") 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]): class KantaImpl(PersistenceMixin, Generic[T]):
"""Internal state and logic for Kanta.""" """Internal state and logic for Kanta."""
@@ -119,7 +114,7 @@ class KantaImpl(PersistenceMixin, Generic[T]):
kanta=self._kanta, kanta=self._kanta,
report=report, report=report,
), ),
on_error=_log_callback_error, on_error=callback_error_reporter("logmigr"),
) )
return return
+3 -11
View File
@@ -12,7 +12,7 @@ from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any 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.diff import diff
from kanta.exceptions import DatabaseError, DataIntegrityError from kanta.exceptions import DatabaseError, DataIntegrityError
from kanta.filelock import LockedFile from kanta.filelock import LockedFile
@@ -21,7 +21,7 @@ from kanta.serialization import JsonSerializer, Serializer
from kanta.serialization.framing import Framer from kanta.serialization.framing import Framer
from kanta.snapshot import SnapshotState from kanta.snapshot import SnapshotState
_logger = logging.getLogger(__name__) _logger = logging.getLogger("kanta")
class PersistenceMixin: class PersistenceMixin:
@@ -110,18 +110,10 @@ class PersistenceMixin:
break break
except DatabaseError as e: except DatabaseError as e:
self.background_error = 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( await self.callback_registry.invoke(
"fatal_error", "fatal_error",
InjectionContext(error=e, kanta=self._kanta), 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) _logger.error("Background flush loop stopped: %s", e)
break break
+1 -1
View File
@@ -20,7 +20,7 @@ from kanta.structs import ChangeRecord, Snapshot
from kanta.serialization.base import Serializer, apply_diff from kanta.serialization.base import Serializer, apply_diff
from kanta.serialization.framing import Framer from kanta.serialization.framing import Framer
_logger = logging.getLogger(__name__) _logger = logging.getLogger("kanta")
@dataclass @dataclass
+2 -2
View File
@@ -10,7 +10,7 @@ from kanta.structs import Snapshot
from kanta.serialization import JsonSerializer, Serializer from kanta.serialization import JsonSerializer, Serializer
from kanta.serialization.framing import Framer, LineFramer from kanta.serialization.framing import Framer, LineFramer
_logger = logging.getLogger(__name__) _logger = logging.getLogger("kanta")
MINDIFFS = 100 MINDIFFS = 100
@@ -69,7 +69,7 @@ class SnapshotState:
self._write(file, version, state, ts, m=m) self._write(file, version, state, ts, m=m)
self._force_pending = False self._force_pending = False
except Exception as exc: except Exception as exc:
_logger.error("snapshot: failed to write snapshot: %r", exc) _logger.error("Kanta snapshot failed: %r", exc)
def _write( def _write(
self, file, version: int, state: dict, now: datetime, m: datetime | None = None self, file, version: int, state: dict, now: datetime, m: datetime | None = None
+1 -1
View File
@@ -13,7 +13,7 @@ from kanta.callbacks import InjectionContext
from kanta.logging import _USER_PATH, LogEvent, emit_event, transaction_logger from kanta.logging import _USER_PATH, LogEvent, emit_event, transaction_logger
from kanta.serialization import restore_data_in_place, struct_to_dict 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): def _build_logfmt(impl, previous: dict, current: dict):