mirror of
https://github.com/kristoferssolo/bunyan-formatter.git
synced 2026-02-04 14:32:06 +00:00
fix(formatter): custom fields
This commit is contained in:
@@ -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
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user