feat: add structured JSON logging
New JsonFormatter emits one JSON object per log line (JSONL). Activated via format = "json" in [logging] config. Default remains "text".
This commit is contained in:
+10
-3
@@ -10,6 +10,7 @@ import sys
|
|||||||
from derp import __version__
|
from derp import __version__
|
||||||
from derp.bot import Bot
|
from derp.bot import Bot
|
||||||
from derp.config import resolve_config
|
from derp.config import resolve_config
|
||||||
|
from derp.log import JsonFormatter
|
||||||
from derp.plugin import PluginRegistry
|
from derp.plugin import PluginRegistry
|
||||||
|
|
||||||
LOG_FORMAT = "%(asctime)s %(levelname)-5s %(name)s %(message)s"
|
LOG_FORMAT = "%(asctime)s %(levelname)-5s %(name)s %(message)s"
|
||||||
@@ -60,11 +61,17 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
level = logging.DEBUG if args.verbose else logging.INFO
|
|
||||||
logging.basicConfig(format=LOG_FORMAT, datefmt=LOG_DATE, level=level)
|
|
||||||
|
|
||||||
config = resolve_config(args.config)
|
config = resolve_config(args.config)
|
||||||
|
|
||||||
|
level = logging.DEBUG if args.verbose else logging.INFO
|
||||||
|
log_fmt = config.get("logging", {}).get("format", "text")
|
||||||
|
if log_fmt == "json":
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setFormatter(JsonFormatter())
|
||||||
|
logging.basicConfig(handlers=[handler], level=level)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(format=LOG_FORMAT, datefmt=LOG_DATE, level=level)
|
||||||
|
|
||||||
log = logging.getLogger("derp")
|
log = logging.getLogger("derp")
|
||||||
log.info("derp %s starting", __version__)
|
log.info("derp %s starting", __version__)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"""Structured logging formatters."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class JsonFormatter(logging.Formatter):
|
||||||
|
"""Emit log records as single-line JSON objects."""
|
||||||
|
|
||||||
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
|
entry: dict[str, str] = {
|
||||||
|
"ts": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
|
||||||
|
"level": record.levelname.lower(),
|
||||||
|
"logger": record.name,
|
||||||
|
"msg": record.getMessage(),
|
||||||
|
}
|
||||||
|
if record.exc_info and not isinstance(record.exc_info, bool) and record.exc_info[0]:
|
||||||
|
entry["exc"] = self.formatException(record.exc_info)
|
||||||
|
return json.dumps(entry, ensure_ascii=False, separators=(",", ":"))
|
||||||
Reference in New Issue
Block a user