Files
utils/src/utils/logging_config.py
T
2026-09-30 13:41:19 +02:00

81 lines
2.8 KiB
Python
Executable File

import logging
from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
from typing import Any
from pydantic import Field
from uvicorn.logging import DefaultFormatter
from utils.settings import BaseSettingsConfig
logger = logging.getLogger(__name__)
def _rotation_namer(log_path: Path, default_name: str) -> str:
"""Place the rotation timestamp before the configured log file extension."""
rotated_path = Path(default_name)
timestamp = rotated_path.name.removeprefix(f"{log_path.name}.")
rotated_name = f"{log_path.stem}.{timestamp}{log_path.suffix}"
return str(rotated_path.with_name(rotated_name))
class LoggingSettings(BaseSettingsConfig):
LOG_LEVEL: str = "INFO"
LOG_FORMAT: str = "%(asctime)s | %(levelprefix)s %(message)s"
LOG_DATE_FORMAT: str = "%Y-%m-%d %H:%M:%S"
LOG_TO_FILE: bool = True
LOG_FILE_PATH: str = "logs/monitoring_backend.log"
LOG_BACKUP_COUNT: int = Field(default=30, ge=0)
def model_post_init(self, __context: Any) -> None:
self.setup_logging()
def setup_logging(self) -> None:
"""Configure application-wide logging with console and optional file output."""
level_name = self.LOG_LEVEL.upper()
level = logging.getLevelNamesMapping().get(level_name)
if level is None:
logger.error("Invalid log level %r; falling back to INFO.", self.LOG_LEVEL)
level = logging.INFO
console_formatter = DefaultFormatter(
fmt=self.LOG_FORMAT,
datefmt=self.LOG_DATE_FORMAT,
)
handlers: list[logging.Handler] = []
console_handler = logging.StreamHandler()
console_handler.setFormatter(console_formatter)
handlers.append(console_handler)
if self.LOG_TO_FILE:
log_path = Path(self.LOG_FILE_PATH)
log_path.parent.mkdir(parents=True, exist_ok=True)
file_handler = TimedRotatingFileHandler(
log_path,
when="midnight",
interval=1,
backupCount=self.LOG_BACKUP_COUNT,
encoding="utf-8",
)
file_handler.namer = lambda default_name: _rotation_namer(
log_path, default_name
)
file_handler.setFormatter(
logging.Formatter(
fmt="%(asctime)s | %(levelname)s | %(message)s",
datefmt=self.LOG_DATE_FORMAT,
)
)
handlers.append(file_handler)
root_logger = logging.getLogger()
root_logger.setLevel(level)
# Replace and close existing handlers to avoid duplicate output and resource leaks.
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
handler.close()
for handler in handlers:
root_logger.addHandler(handler)