Files
BanGUI/backend/app/utils/json_formatter.py
Lukas 7ec80fdeec refactor(logging): replace structlog with stdlib logging compat layer
- Remove structlog dependency from backend/pyproject.toml
- Add app.utils.logging_compat shim for keyword-arg logging API
- Add app.utils.json_formatter for JSON log output with extra fields
- Update all backend modules to use logging_compat.get_logger()
- Update docstrings in log_sanitizer.py and json_formatter.py
- Update test comment in test_async_utils.py
- Record 406 failing tests in Docs/Tasks.md for tracking
2026-05-10 13:37:54 +02:00

86 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""JSON formatter for stdlib logging that preserves extra fields.
A single logging.Formatter subclass that serialises any keyword arguments
passed via ``extra=`` into the JSON output alongside the standard record
attributes.
"""
from __future__ import annotations
import json
import logging
from datetime import datetime, timezone
from typing import Any
# Attributes that belong to the standard LogRecord and should NOT be
# treated as user-supplied extra fields.
_STD_RECORD_ATTRS: frozenset[str] = frozenset(
{
"name",
"msg",
"args",
"levelname",
"levelno",
"pathname",
"filename",
"module",
"exc_info",
"exc_text",
"stack_info",
"lineno",
"funcName",
"created",
"msecs",
"relativeCreated",
"thread",
"threadName",
"processName",
"process",
"message",
"asctime",
"taskName",
}
)
class JSONFormatter(logging.Formatter):
"""Format log records as JSON lines, including extra fields.
Usage::
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logging.getLogger().addHandler(handler)
Output keys:
- ``event`` the log message
- ``level`` lower-cased level name
- ``timestamp`` ISO-8601 UTC timestamp
- ``logger`` logger name
- any ``extra`` fields supplied by the caller
"""
def format(self, record: logging.LogRecord) -> str:
"""Return a JSON string for *record*."""
log_dict: dict[str, Any] = {
"event": record.getMessage(),
"level": record.levelname.lower(),
"timestamp": (
datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat()
),
"logger": record.name,
}
# Merge any extra fields attached to the record.
for key, value in record.__dict__.items():
if key not in _STD_RECORD_ATTRS:
log_dict[key] = value
# Include exception info when present.
if record.exc_info and not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
log_dict["exception"] = record.exc_text
return json.dumps(log_dict, default=str)