From e5032c7e59160acf1f36812f26183ecf1685a557 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 4 May 2026 22:03:43 +0200 Subject: [PATCH] feat: key=value logging formatter --- app/logging_setup.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 app/logging_setup.py diff --git a/app/logging_setup.py b/app/logging_setup.py new file mode 100644 index 0000000..c14fa9c --- /dev/null +++ b/app/logging_setup.py @@ -0,0 +1,31 @@ +import logging +import sys + + +class KeyValueFormatter(logging.Formatter): + """Formats records as `LEVEL msg key1=val1 key2=val2`.""" + + def format(self, record: logging.LogRecord) -> str: + base = f"{record.levelname} {record.getMessage()}" + # extras land in record.__dict__ but mixed with stdlib keys; filter to known-extra keys + reserved = { + "name", "msg", "args", "levelname", "levelno", "pathname", "filename", + "module", "exc_info", "exc_text", "stack_info", "lineno", "funcName", + "created", "msecs", "relativeCreated", "thread", "threadName", + "processName", "process", "message", "taskName", + } + extras = {k: v for k, v in record.__dict__.items() if k not in reserved} + if extras: + base += " " + " ".join(f"{k}={v}" for k, v in extras.items()) + if record.exc_info: + base += "\n" + self.formatException(record.exc_info) + return base + + +def setup_logging(level: str = "INFO") -> None: + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(KeyValueFormatter()) + root = logging.getLogger() + root.handlers.clear() + root.addHandler(handler) + root.setLevel(level.upper())