81 lines
2.8 KiB
Python
Executable File
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)
|