"""Logger configuration module. Provides unified logging that writes simultaneously to the console and a rotating log file. """ import os import sys import logging from datetime import datetime from logging.handlers import RotatingFileHandler def _ensure_utf8_stdout(): """Force stdout/stderr to UTF-8. Fixes garbled non-ASCII output on the Windows console. """ if sys.platform == 'win32': # On Windows, reconfigure the standard streams to UTF-8. if hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(encoding='utf-8', errors='replace') if hasattr(sys.stderr, 'reconfigure'): sys.stderr.reconfigure(encoding='utf-8', errors='replace') # Directory that holds rotated log files. LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'logs') def setup_logger(name: str = 'mirofish', level: int = logging.DEBUG) -> logging.Logger: """Configure and return a logger. Args: name: Logger name. level: Minimum log level for the logger. Returns: The configured logger. """ os.makedirs(LOG_DIR, exist_ok=True) logger = logging.getLogger(name) logger.setLevel(level) # Prevent propagation to the root logger to avoid duplicate output. logger.propagate = False # If handlers are already attached, do not re-add them. if logger.handlers: return logger detailed_formatter = logging.Formatter( '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) simple_formatter = logging.Formatter( '[%(asctime)s] %(levelname)s: %(message)s', datefmt='%H:%M:%S' ) # 1. File handler — detailed log, named by date and rotated by size. log_filename = datetime.now().strftime('%Y-%m-%d') + '.log' file_handler = RotatingFileHandler( os.path.join(LOG_DIR, log_filename), maxBytes=10 * 1024 * 1024, # 10MB backupCount=5, encoding='utf-8' ) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(detailed_formatter) # 2. Console handler — concise log, INFO and above. # Ensure UTF-8 on Windows so non-ASCII characters render correctly. _ensure_utf8_stdout() console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) console_handler.setFormatter(simple_formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger def get_logger(name: str = 'mirofish') -> logging.Logger: """Return an existing logger by name, creating it lazily if needed. Args: name: Logger name. Returns: The logger instance. """ logger = logging.getLogger(name) if not logger.handlers: return setup_logger(name) return logger # Default module-level logger. logger = setup_logger() # Convenience module-level helpers. def debug(msg, *args, **kwargs): logger.debug(msg, *args, **kwargs) def info(msg, *args, **kwargs): logger.info(msg, *args, **kwargs) def warning(msg, *args, **kwargs): logger.warning(msg, *args, **kwargs) def error(msg, *args, **kwargs): logger.error(msg, *args, **kwargs) def critical(msg, *args, **kwargs): logger.critical(msg, *args, **kwargs)