""" Logging configuration: console (colored) + rotating file handler. """ import logging import sys from pathlib import Path from logging.handlers import RotatingFileHandler _LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(name)-30s | %(message)s" _DATE_FORMAT = "%Y-%m-%d %H:%M:%S" def setup_logger( name: str = "sen2sr", level: int = logging.INFO, log_file: str | None = "sen2sr.log", max_bytes: int = 10 * 1024 * 1024, # 10 MB backup_count: int = 3, ) -> logging.Logger: """ Create (or retrieve) a logger with a stream handler and optional file handler. Parameters ---------- name : Logger name (hierarchical, e.g. "sen2sr.pipeline"). level : Logging level (default INFO). log_file : Path to log file; None disables file logging. max_bytes : Max file size before rotation. backup_count: Number of rotated files to keep. Returns ------- Configured logging.Logger instance. """ logger = logging.getLogger(name) if logger.handlers: # Already configured — avoid duplicate handlers on repeated calls return logger logger.setLevel(level) formatter = logging.Formatter(_LOG_FORMAT, datefmt=_DATE_FORMAT) # ── Console handler ──────────────────────────────────────────────────────── console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(formatter) console_handler.setLevel(level) logger.addHandler(console_handler) # ── Rotating file handler ───────────────────────────────────────────────── if log_file: log_path = Path(log_file) log_path.parent.mkdir(parents=True, exist_ok=True) file_handler = RotatingFileHandler( log_path, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8", ) file_handler.setFormatter(formatter) file_handler.setLevel(level) logger.addHandler(file_handler) return logger def get_logger(name: str) -> logging.Logger: """Convenience: return a child logger under the 'sen2sr' hierarchy.""" return logging.getLogger(f"s2sr_pipe.{name}")