fix(formatter): custom fields

This commit is contained in:
2024-09-12 15:21:59 +03:00
parent ad7db29ac1
commit 44ffa512a1
3 changed files with 207 additions and 28 deletions

View File

@@ -2,21 +2,46 @@ import json
import logging
import socket
import time
import traceback
from logging import Formatter, LogRecord
from pathlib import Path
from typing import ClassVar, Optional
from typing import Any, Optional
DEFAULT_FIELDS = {
"name",
"msg",
"args",
"levelname",
"levelno",
"pathname",
"filename",
"module",
"exc_info",
"exc_text",
"stack_info",
"lineno",
"funcName",
"created",
"msecs",
"relativeCreated",
"thread",
"threadName",
"processName",
"process",
"taskName",
}
LEVEL_MAP = {
logging.NOTSET: 10,
logging.DEBUG: 20,
logging.INFO: 30,
logging.WARNING: 40,
logging.ERROR: 50,
logging.CRITICAL: 60,
}
class BunyanFormatter(Formatter):
LEVEL_MAP: ClassVar[dict[int, int]] = {
logging.NOTSET: 10,
logging.DEBUG: 20,
logging.INFO: 30,
logging.WARNING: 40,
logging.ERROR: 50,
logging.CRITICAL: 60,
}
def __init__(self, project_name: str, project_root: Path) -> None:
super().__init__()
self.project_name = project_name
@@ -24,8 +49,6 @@ class BunyanFormatter(Formatter):
self.hostname = socket.gethostname()
def format(self, record: LogRecord) -> str:
hostname = socket.gethostname()
file_path = Path(record.pathname)
try:
relative_path = file_path.relative_to(self.project_root)
@@ -36,9 +59,9 @@ class BunyanFormatter(Formatter):
"v": 0,
"name": self.project_name,
"msg": record.getMessage(),
"level": self.LEVEL_MAP.get(record.levelno, record.levelno),
"level": LEVEL_MAP.get(record.levelno, record.levelno),
"levelname": record.levelname,
"hostname": hostname,
"hostname": self.hostname,
"pid": record.process,
"time": self.formatTime(record),
"target": record.name,
@@ -46,6 +69,15 @@ class BunyanFormatter(Formatter):
"file": str(relative_path),
}
# Handle extra fields
extra_fields = {k: v for k, v in record.__dict__.items() if k not in DEFAULT_FIELDS}
if extra_fields:
log_entry["extra"] = extra_fields
# Handle exception information
if record.exc_info and all(record.exc_info):
log_entry["err"] = self._format_exception(record)
return json.dumps(log_entry)
def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: # noqa: N802
@@ -56,3 +88,30 @@ class BunyanFormatter(Formatter):
t = time.strftime("%Y-%m-%dT%H:%M:%S", ct)
s = f"{t}.{int(record.msecs):03d}Z"
return s
def _format_exception(self, record: LogRecord) -> dict[str, Any]:
exc_info = record.exc_info
if exc_info is None or len(exc_info) != 3:
return {}
exc_type, exc_value, exc_traceback = exc_info
if exc_type is None or exc_value is None or exc_traceback is None:
return {}
stack = traceback.extract_tb(exc_traceback)
return {
"message": str(exc_value),
"name": getattr(exc_type, "__name__", "UnknownException"),
"stack": [
{
"file": frame.filename,
"line": frame.lineno,
"function": frame.name,
"text": frame.line.strip() if frame.line is not None else "",
}
for frame in stack
],
}