213 lines
7.6 KiB
Python
213 lines
7.6 KiB
Python
|
|
#!/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
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|