""" Logging configuration module Provides unified log management, writing to both console and file """ import os import sys import logging from datetime import datetime from logging.handlers import RotatingFileHandler def _ensure_utf8_stdout(): """ Ensure stdout/stderr use UTF-8 encoding. Fixes garbled output in Windows consoles. """ if sys.platform == 'win32': # Reconfigure standard streams to UTF-8 on Windows 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') # Log directory 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: """ Set up a logger Args: name: Logger name level: Log level Returns: Configured logger instance """ # Ensure the log directory exists os.makedirs(LOG_DIR, exist_ok=True) # Create logger logger = logging.getLogger(name) logger.setLevel(level) # Prevent log records from propagating to the root logger to avoid duplicate output logger.propagate = False # Skip adding handlers if they already exist if logger.handlers: return logger # Log formatters 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 logs (date-stamped filename with rotation) 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 logs (INFO and above) # Ensure UTF-8 encoding on Windows to avoid garbled output _ensure_utf8_stdout() console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) console_handler.setFormatter(simple_formatter) # Register handlers logger.addHandler(file_handler) logger.addHandler(console_handler) return logger def get_logger(name: str = 'mirofish') -> logging.Logger: """ Get a logger, creating it if it does not exist Args: name: Logger name Returns: Logger instance """ logger = logging.getLogger(name) if not logger.handlers: return setup_logger(name) return logger # Create default logger logger = setup_logger() # Convenience functions 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)