logger.py 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. from __future__ import annotations
  2. import os
  3. import logging
  4. from pathlib import Path
  5. from logging.handlers import RotatingFileHandler
  6. from typing import Optional
  7. _CONFIGURED = False
  8. def _project_root() -> Path:
  9. # Resolve repo root from this file location: app/core/logger.py -> repo/app/core
  10. return Path(__file__).resolve().parents[2]
  11. def configure_logging(level: Optional[str] = None, log_dir: Optional[str] = None) -> None:
  12. global _CONFIGURED
  13. if _CONFIGURED:
  14. return
  15. # Determine log level
  16. level_name = (level or os.getenv("LOG_LEVEL") or "INFO").upper()
  17. log_level = getattr(logging, level_name, logging.INFO)
  18. # Determine logs directory
  19. base_dir = Path(log_dir) if log_dir else _project_root() / "logs"
  20. base_dir.mkdir(parents=True, exist_ok=True)
  21. # Root logger configuration
  22. root = logging.getLogger()
  23. root.setLevel(log_level)
  24. fmt = logging.Formatter(
  25. fmt="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
  26. datefmt="%Y-%m-%d %H:%M:%S",
  27. )
  28. file_handler = RotatingFileHandler(base_dir / "app.log", maxBytes=1_000_000, backupCount=3)
  29. file_handler.setFormatter(fmt)
  30. file_handler.setLevel(log_level)
  31. root.addHandler(file_handler)
  32. # Stream warnings+ to stderr for container visibility
  33. stream = logging.StreamHandler()
  34. stream.setLevel(logging.WARNING)
  35. stream.setFormatter(fmt)
  36. root.addHandler(stream)
  37. _CONFIGURED = True
  38. def get_logger(name: Optional[str] = None) -> logging.Logger:
  39. if not _CONFIGURED:
  40. configure_logging()
  41. return logging.getLogger(name or "app")