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.
This commit is contained in:
2026-09-16 02:55:10 +00:00
parent 44c1ed191e
commit a4efbca55b
5 changed files with 31 additions and 19 deletions
-1
View File
@@ -8,7 +8,6 @@ import contextlib
import importlib
import importlib.metadata
import importlib.util
import logging
import sys
import tempfile
from pathlib import Path
+19 -12
View File
@@ -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.<kind> <name (docstring)> failed:
<error>`` for each failing callback; invoke continues with the rest.
The returned callable logs ``Kanta.<kind> <name (docstring)> 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
+7 -4
View File
@@ -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
+3 -1
View File
@@ -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:
+2 -1
View File
@@ -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(),