""" Unified logging module. Provides a pre-configured logger with console + rotating file handlers. """ import logging import os from logging.handlers import TimedRotatingFileHandler def setup_logger(name='app', log_dir=None, level='INFO', max_keep_days=30): """Create and configure a logger. Args: name: Logger name (used as basename for log file). log_dir: Directory for log files. Defaults to /logs. level: Logging level string (DEBUG, INFO, WARNING, ERROR). max_keep_days: Number of days to retain rotated log files. Returns: Configured logging.Logger instance. """ if log_dir is None: log_dir = 'logs' # Resolve relative paths against project root project_root = os.path.dirname(os.path.abspath(__file__)) if not os.path.isabs(log_dir): log_dir = os.path.join(project_root, log_dir) os.makedirs(log_dir, exist_ok=True) logger = logging.getLogger(name) logger.setLevel(getattr(logging, level.upper(), logging.INFO)) # Avoid adding duplicate handlers on repeated calls if logger.handlers: return logger fmt = logging.Formatter( '%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # Console handler ch = logging.StreamHandler() ch.setLevel(getattr(logging, level.upper(), logging.INFO)) ch.setFormatter(fmt) logger.addHandler(ch) # File handler — rotate daily, keep max_keep_days backups log_file = os.path.join(log_dir, f'{name}.log') fh = TimedRotatingFileHandler( log_file, when='midnight', interval=1, backupCount=max_keep_days, encoding='utf-8' ) fh.setLevel(getattr(logging, level.upper(), logging.INFO)) fh.setFormatter(fmt) fh.suffix = '%Y-%m-%d' logger.addHandler(fh) return logger def get_logger(name='app'): """Get an existing logger by name (does not reconfigure).""" return logging.getLogger(name)