from logging import Formatter, LogRecord, LoggerAdapter from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Any, MutableMapping # skip natural LogRecord attributes # http://docs.python.org/library/logging.html#logrecord-attributes RESERVED_ATTRS: set[str] = { 'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename', 'funcName', 'levelname', 'levelno', 'lineno', 'module', 'msecs', 'message', 'msg', 'name', 'pathname', 'process', 'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName'} class MergedLoggerAdapter(LoggerAdapter): def process(self, msg: "Any", kwargs: "MutableMapping[str, Any]") -> tuple["Any", "MutableMapping[str, Any]"]: kwargs["extra"] = {**self.extra, **kwargs.get("extra", {})} return msg, kwargs class ExtraFormatter(Formatter): def format(self, record: LogRecord): s = super().format(record) extras = {} for key, value in record.__dict__.items(): if key in {"title", "taskName"} or key in RESERVED_ATTRS or (hasattr(key, "startswith") and key.startswith("_")): continue extras[key] = value if len(extras) == 0: return s str_extras = str(extras) if len(str_extras) >= 300: str_extras = str_extras[:297] + "..." s += " " + str_extras return s