from __future__ import annotations import os import logging from pathlib import Path from logging.handlers import RotatingFileHandler from typing import Optional _CONFIGURED = False def _project_root() -> Path: # Resolve repo root from this file location: app/core/logger.py -> repo/app/core return Path(__file__).resolve().parents[2] def configure_logging(level: Optional[str] = None, log_dir: Optional[str] = None) -> None: global _CONFIGURED if _CONFIGURED: return # Determine log level level_name = (level or os.getenv("LOG_LEVEL") or "INFO").upper() log_level = getattr(logging, level_name, logging.INFO) # Determine logs directory base_dir = Path(log_dir) if log_dir else _project_root() / "logs" base_dir.mkdir(parents=True, exist_ok=True) # Root logger configuration root = logging.getLogger() root.setLevel(log_level) fmt = logging.Formatter( fmt="%(asctime)s [%(levelname)s] %(name)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) file_handler = RotatingFileHandler(base_dir / "app.log", maxBytes=1_000_000, backupCount=3) file_handler.setFormatter(fmt) file_handler.setLevel(log_level) root.addHandler(file_handler) # Stream warnings+ to stderr for container visibility stream = logging.StreamHandler() stream.setLevel(logging.WARNING) stream.setFormatter(fmt) root.addHandler(stream) _CONFIGURED = True def get_logger(name: Optional[str] = None) -> logging.Logger: if not _CONFIGURED: configure_logging() return logging.getLogger(name or "app")