- README: 补充项目概述、技术栈、架构图(Mermaid)、插件生命周期时序图 - build.py: 补充模块和函数 docstring - core/__init__.py: 修正模块说明(工具箱→插件系统) - log.py: 修复过时注释,清理死代码,抑制第三方库日志刷屏 - plugin_registry.py: 修正矛盾注释,补充缺失日志,隐藏远程路径 - plugins.py: 修正过时注释,清理死代码和无用导入 - plugins_card.py: 修正错误类名注释,补充全量 docstring - main.py: 修正错误注释,补充启动/退出/插件加载日志
204 lines
6.3 KiB
Python
204 lines
6.3 KiB
Python
"""
|
||
日志模块 - 提供统一的日志记录功能
|
||
|
||
功能说明:
|
||
- 日志写入文件 (logs/log_file.log)
|
||
- 日志输出到控制台
|
||
- 日志显示到 UI 的 TextEdit 组件
|
||
|
||
架构设计:
|
||
- 使用组合模式避免 PySide6 QObject.emit() 方法冲突
|
||
- LogHandler: 独立的日志处理器,继承自 logging.Handler
|
||
- FrameLog: UI 组件,负责日志配置和显示
|
||
|
||
日志格式:%(asctime)s [%(levelname)s] %(message)s
|
||
时间格式:%Y-%m-%d %H:%M:%S
|
||
"""
|
||
|
||
import logging
|
||
from pathlib import Path
|
||
from typing import Callable, Optional
|
||
|
||
from PySide6.QtCore import QObject, Signal
|
||
from PySide6.QtWidgets import QFrame
|
||
|
||
from .log_ui import Ui_FrameLog
|
||
|
||
# 日志配置常量
|
||
LOG_FILE = "logs/log_file.log" # 日志文件路径
|
||
CONSOLE_OUTPUT = True # 是否输出到控制台
|
||
|
||
|
||
class LogHandler(logging.Handler):
|
||
"""
|
||
独立的日志处理器
|
||
|
||
继承自 logging.Handler,用于处理日志记录的格式化输出
|
||
使用回调函数机制避免与 PySide6 QObject.emit() 方法名冲突
|
||
|
||
设计原因:
|
||
- logging.Handler.emit() 和 QObject.emit() 方法签名冲突
|
||
- 使用组合模式,将回调函数作为日志传递的桥梁
|
||
"""
|
||
|
||
def __init__(self, callback: Callable[[str], None]) -> None:
|
||
"""
|
||
初始化日志处理器
|
||
|
||
Args:
|
||
callback: 回调函数,接收格式化后的日志消息字符串
|
||
"""
|
||
super().__init__()
|
||
self.callback = callback
|
||
|
||
def emit(self, record: logging.LogRecord) -> None:
|
||
"""
|
||
处理日志记录
|
||
|
||
当日志系统调用此方法时,将格式化后的日志消息传递给回调函数
|
||
|
||
Args:
|
||
record: 日志记录对象,包含级别、消息等信息
|
||
|
||
异常处理:
|
||
- RecursionError: 防止递归调用导致无限循环
|
||
- 其他异常: 使用 handleError 记录错误
|
||
"""
|
||
try:
|
||
msg = self.format(record)
|
||
self.callback(msg)
|
||
except RecursionError:
|
||
self.handleError(record)
|
||
except Exception:
|
||
self.handleError(record)
|
||
|
||
|
||
class FrameLog(QFrame, Ui_FrameLog):
|
||
"""
|
||
日志显示框架组件
|
||
|
||
继承自 QFrame(Qt 框架组件)和 Ui_FrameLog(自动生成的 UI 界面)
|
||
负责日志系统的配置和 UI 显示功能
|
||
|
||
功能:
|
||
- 初始化日志系统(文件、控制台、UI)
|
||
- 将日志消息显示到 TextEdit 组件
|
||
- 通过信号机制实现线程安全的 UI 更新
|
||
"""
|
||
|
||
# PySide6 信号定义,用于跨线程传递日志消息
|
||
log_signal = Signal(str)
|
||
|
||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||
"""
|
||
初始化日志框架
|
||
|
||
Args:
|
||
parent: 父组件对象,默认为 None
|
||
"""
|
||
QFrame.__init__(self, parent)
|
||
self.setupUi(self)
|
||
self._init_logging()
|
||
self._connect_signal()
|
||
|
||
def _init_logging(self) -> None:
|
||
"""
|
||
初始化日志系统
|
||
|
||
配置日志系统的各个组件:
|
||
- 设置日志级别为 DEBUG
|
||
- 配置日志格式化器
|
||
- 设置文件日志处理器(可选)
|
||
- 设置控制台日志处理器(可选)
|
||
- 配置 UI 日志处理器
|
||
"""
|
||
logger = logging.getLogger()
|
||
logger.setLevel(logging.DEBUG)
|
||
|
||
# 抑制第三方库 DEBUG 日志刷屏
|
||
logging.getLogger("PySide6").setLevel(logging.WARNING)
|
||
logging.getLogger("qt_material").setLevel(logging.WARNING)
|
||
|
||
# 创建日志格式化器:时间 [级别] 消息
|
||
formatter = logging.Formatter(
|
||
"%(asctime)s [%(levelname)s] %(message)s",
|
||
datefmt="%Y-%m-%d %H:%M:%S"
|
||
)
|
||
|
||
# 配置文件日志处理器(如果启用)
|
||
if LOG_FILE:
|
||
self._setup_file_handler(logger, formatter)
|
||
|
||
# 配置控制台日志处理器(如果启用)
|
||
if CONSOLE_OUTPUT:
|
||
self._setup_console_handler(logger, formatter)
|
||
|
||
# 配置 UI 日志处理器
|
||
self._log_handler = LogHandler(self._update_log_display)
|
||
self._log_handler.setFormatter(formatter)
|
||
self._log_handler.setLevel(logging.DEBUG)
|
||
logger.addHandler(self._log_handler)
|
||
|
||
def _setup_file_handler(self, logger: logging.Logger, formatter: logging.Formatter) -> None:
|
||
"""
|
||
配置日志文件处理器
|
||
|
||
将日志写入指定文件,支持 UTF-8 编码
|
||
|
||
Args:
|
||
logger: 日志记录器实例
|
||
formatter: 日志格式化器
|
||
|
||
异常处理:
|
||
- OSError: 操作系统错误(如磁盘满)
|
||
- PermissionError: 权限不足
|
||
- 失败时降级到控制台输出
|
||
"""
|
||
try:
|
||
log_path = Path(LOG_FILE)
|
||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||
file_handler = logging.FileHandler(log_path, encoding='utf-8')
|
||
file_handler.setFormatter(formatter)
|
||
file_handler.setLevel(logging.DEBUG)
|
||
logger.addHandler(file_handler)
|
||
except (OSError, PermissionError) as e:
|
||
logging.warning(f"无法创建日志文件 {LOG_FILE}: {e}")
|
||
logging.warning("日志将仅输出到控制台")
|
||
|
||
def _setup_console_handler(self, logger: logging.Logger, formatter: logging.Formatter) -> None:
|
||
"""
|
||
配置日志控制台处理器
|
||
|
||
将日志输出到标准控制台(stdout)
|
||
|
||
Args:
|
||
logger: 日志记录器实例
|
||
formatter: 日志格式化器
|
||
|
||
注意:
|
||
- 控制台输出级别设置为 INFO,只显示重要信息
|
||
- DEBUG 级别的日志不会输出到控制台
|
||
"""
|
||
console_handler = logging.StreamHandler()
|
||
console_handler.setFormatter(formatter)
|
||
console_handler.setLevel(logging.INFO)
|
||
logger.addHandler(console_handler)
|
||
|
||
def _connect_signal(self) -> None:
|
||
"""
|
||
连接日志信号到 UI 更新方法
|
||
|
||
将 log_signal 信号连接到 _update_log_display 方法
|
||
实现日志消息的线程安全传递
|
||
"""
|
||
self.log_signal.connect(self._update_log_display)
|
||
|
||
def _update_log_display(self, message: str) -> None:
|
||
"""更新 UI 的日志显示组件。
|
||
|
||
将日志消息追加到 TextEdit 组件中显示。
|
||
|
||
Args:
|
||
message: 格式化后的日志消息字符串
|
||
"""
|
||
self.textEdit.append(message) |