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
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|