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
This commit is contained in:
63
backend/app/utils/logging_compat.py
Normal file
63
backend/app/utils/logging_compat.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Compatibility shim providing keyword-argument logging API on top of stdlib logging.
|
||||
|
||||
This module lets the rest of the codebase keep the keyword-argument logging
|
||||
style (``log.info("event", key=value)``) while using only the Python standard
|
||||
library ``logging`` module underneath.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
|
||||
class _CompatLogger:
|
||||
"""Wraps a stdlib :class:`logging.Logger` to accept keyword arguments."""
|
||||
|
||||
def __init__(self, logger: logging.Logger) -> None:
|
||||
self._logger = logger
|
||||
|
||||
def _log(self, level: int, event: str, **kwargs: Any) -> None:
|
||||
exc_info = kwargs.pop("exc_info", None)
|
||||
extra = kwargs if kwargs else None
|
||||
self._logger.log(level, event, exc_info=exc_info, extra=extra)
|
||||
|
||||
def debug(self, event: str, **kwargs: Any) -> None:
|
||||
self._log(logging.DEBUG, event, **kwargs)
|
||||
|
||||
def info(self, event: str, **kwargs: Any) -> None:
|
||||
self._log(logging.INFO, event, **kwargs)
|
||||
|
||||
def warning(self, event: str, **kwargs: Any) -> None:
|
||||
self._log(logging.WARNING, event, **kwargs)
|
||||
|
||||
def warn(self, event: str, **kwargs: Any) -> None:
|
||||
self._log(logging.WARNING, event, **kwargs)
|
||||
|
||||
def error(self, event: str, **kwargs: Any) -> None:
|
||||
self._log(logging.ERROR, event, **kwargs)
|
||||
|
||||
def critical(self, event: str, **kwargs: Any) -> None:
|
||||
self._log(logging.CRITICAL, event, **kwargs)
|
||||
|
||||
def exception(self, event: str, **kwargs: Any) -> None:
|
||||
self._log(logging.ERROR, event, exc_info=True, **kwargs)
|
||||
|
||||
def bind(self, **kwargs: Any) -> "_CompatLogger":
|
||||
"""Return a new logger with bound context (no-op for stdlib)."""
|
||||
return self
|
||||
|
||||
|
||||
def get_logger(name: str | None = None) -> _CompatLogger:
|
||||
"""Get a compatibility logger wrapping the stdlib logger for *name*.
|
||||
|
||||
If *name* is ``None`` the caller's module name is used.
|
||||
"""
|
||||
if name is None:
|
||||
import sys
|
||||
|
||||
# Walk up the stack to find the caller's module.
|
||||
frame = sys._getframe(1)
|
||||
module = frame.f_globals.get("__name__", "__main__")
|
||||
name = module
|
||||
return _CompatLogger(logging.getLogger(name))
|
||||
Reference in New Issue
Block a user