#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 日志管理器模块 提供异步日志功能(使用 QueueHandler + QueueListener) """ import logging from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler import queue import os import config from version import VERSION class LoggerManager: """日志管理器(单例)""" _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(LoggerManager, cls).__new__(cls) cls._instance._initialized = False return cls._instance def __init__(self): if self._initialized: return # 私有状态 self._log_queue = None self._queue_listener = None self._logger = None self._initialized = True # ==================== 状态访问(只读属性)==================== @property def logger(self): """获取logger对象(只读)""" return self._logger @property def log_queue(self): """获取日志队列(只读)""" return self._log_queue # ==================== 业务方法 ==================== def init_logging(self, log_level=logging.INFO, log_file=None, max_bytes=None, backup_count=None): """ 初始化异步日志系统(使用 QueueHandler + QueueListener) Args: log_level: 日志级别,默认 INFO log_file: 日志文件路径,默认使用 config.LOG_FILE max_bytes: 单个日志文件最大大小(字节),默认使用 config.LOG_MAX_BYTES backup_count: 保留的备份文件数量,默认使用 config.LOG_BACKUP_COUNT """ if log_file is None: log_file = config.LOG_FILE if max_bytes is None: max_bytes = config.LOG_MAX_BYTES if backup_count is None: backup_count = config.LOG_BACKUP_COUNT try: # 创建日志队列(无界队列) self._log_queue = queue.Queue(-1) # 确保日志文件所在的目录存在 log_dir = os.path.dirname(log_file) if log_dir: # 如果日志路径包含目录 try: os.makedirs(log_dir, exist_ok=True) except Exception as e: print(f"[WARN] 无法创建日志目录 {log_dir}: {e}") # 尝试创建文件Handler(带日志轮转) try: file_handler = RotatingFileHandler( log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8', mode='a' # 追加模式,确保不覆盖 ) except Exception as e: # 如果RotatingFileHandler不可用,降级为普通FileHandler print(f"[WARN] RotatingFileHandler不可用,使用普通FileHandler: {e}") try: file_handler = logging.FileHandler(log_file, encoding='utf-8', mode='a') except Exception as e2: # 如果文件Handler创建失败,只使用控制台Handler print(f"[WARN] 无法创建文件Handler,仅使用控制台输出: {e2}") file_handler = None # 自定义Formatter,包含版本信息 class CustomFormatter(logging.Formatter): """自定义日志格式,包含版本信息和行号""" def format(self, record): record.version = VERSION return super().format(record) # 如果file_handler存在,设置格式和级别 if file_handler is not None: file_handler.setFormatter(CustomFormatter( '%(asctime)s [v%(version)s] [%(levelname)s] %(filename)s:%(lineno)d - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' )) file_handler.setLevel(log_level) # 创建控制台Handler(保留原有的print输出) console_handler = logging.StreamHandler() console_handler.setFormatter(CustomFormatter( '[v%(version)s] [%(levelname)s] %(filename)s:%(lineno)d - %(message)s' )) console_handler.setLevel(log_level) # 创建QueueListener(后台线程处理日志写入) # 如果file_handler为None,只使用console_handler handlers = [console_handler] if file_handler is not None: handlers.append(file_handler) self._queue_listener = QueueListener( self._log_queue, *handlers, respect_handler_level=True ) self._queue_listener.start() # 创建QueueHandler(用于记录日志) queue_handler = QueueHandler(self._log_queue) # 配置根logger self._logger = logging.getLogger() self._logger.addHandler(queue_handler) self._logger.setLevel(log_level) # 避免日志向上传播到其他logger self._logger.propagate = False # 添加启动标记 self._logger.info("=" * 60) self._logger.info("程序启动 - 日志系统初始化") self._logger.info(f"版本: {VERSION}") self._logger.info(f"日志文件: {log_file}") self._logger.info("=" * 60) return True except Exception as e: # 如果日志初始化失败,至少保证程序能运行 print(f"[ERROR] 日志系统初始化失败: {e}") import traceback try: traceback.print_exc() except: pass return False def stop_logging(self): """停止日志系统(程序退出时调用)""" try: if self._logger: # 确保所有日志都写入 self._logger.info("程序退出,正在保存日志...") import time as std_time std_time.sleep(0.5) # 给一点时间让日志写入 if self._queue_listener: self._queue_listener.stop() if self._logger: # 等待队列中的日志处理完成 if self._log_queue: import time as std_time timeout = 5 start = std_time.time() while not self._log_queue.empty() and (std_time.time() - start) < timeout: std_time.sleep(0.1) print("[LOG] 日志系统已停止") except Exception as e: print(f"[ERROR] 停止日志系统失败: {e}") # 创建全局单例实例 logger_manager = LoggerManager() # ==================== 向后兼容的函数接口 ==================== def init_logging(log_level=logging.INFO, log_file=None, max_bytes=None, backup_count=None): """初始化日志系统(向后兼容接口)""" return logger_manager.init_logging(log_level, log_file, max_bytes, backup_count) def stop_logging(): """停止日志系统(向后兼容接口)""" return logger_manager.stop_logging() def get_logger(): """ 获取全局logger对象(向后兼容接口) 如果日志系统未初始化,返回None(此时可以使用print作为fallback) """ return logger_manager.logger