Apply Black formatter to the entire codebase for consistent code style. Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
123 lines
3.8 KiB
Python
123 lines
3.8 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
自定义 logging Handler,将日志输出到 LogText 组件
|
||
"""
|
||
|
||
import logging
|
||
import re
|
||
from typing import Optional
|
||
from gui.widgets.log_text import LogText
|
||
|
||
|
||
class GuiTextHandler(logging.Handler):
|
||
"""
|
||
将日志输出到 GUI LogText 组件的 Handler
|
||
|
||
这个 Handler 桥接了 Python 标准 logging 模块和 GUI 的 LogText 组件,
|
||
使得使用 logging 模块的代码可以自动将日志输出到 GUI 界面。
|
||
"""
|
||
|
||
def __init__(self, log_text: Optional[LogText] = None):
|
||
"""
|
||
初始化 Handler
|
||
|
||
Args:
|
||
log_text: LogText 组件实例,可以为 None,稍后通过 set_log_text 设置
|
||
"""
|
||
super().__init__()
|
||
self.log_text = log_text
|
||
|
||
# 映射 logging 级别到 LogText 级别
|
||
self.level_map = {
|
||
logging.INFO: "INFO",
|
||
logging.WARNING: "WARNING",
|
||
logging.ERROR: "ERROR",
|
||
logging.DEBUG: "DEBUG",
|
||
logging.CRITICAL: "ERROR",
|
||
}
|
||
|
||
def set_log_text(self, log_text: LogText):
|
||
"""
|
||
设置或更新 LogText 组件引用
|
||
|
||
Args:
|
||
log_text: LogText 组件实例
|
||
"""
|
||
self.log_text = log_text
|
||
|
||
def emit(self, record: logging.LogRecord):
|
||
"""
|
||
实现日志输出
|
||
|
||
Args:
|
||
record: logging.LogRecord 对象
|
||
"""
|
||
if not self.log_text:
|
||
return
|
||
|
||
try:
|
||
# 获取日志级别
|
||
level = self.level_map.get(record.levelno, "INFO")
|
||
|
||
# 只获取消息内容,不包含时间戳和级别(LogText.log() 会添加)
|
||
message = record.getMessage()
|
||
|
||
# 移除消息中可能存在的冗余级别标记(如 "[INFO] "、"[ERROR] " 等)
|
||
# 这是因为有些代码在消息中已经包含了级别标记
|
||
message = self._strip_redundant_level_prefix(message)
|
||
|
||
# 定义更新函数
|
||
def update():
|
||
"""在主线程中更新 GUI"""
|
||
try:
|
||
# LogText.log() 会自动添加时间戳和级别
|
||
self.log_text.log(message, level)
|
||
except Exception:
|
||
# 如果 log 失败,忽略错误避免递归
|
||
pass
|
||
|
||
# 尝试使用 after 确保在主线程更新
|
||
import tkinter as tk
|
||
|
||
try:
|
||
# 尝试获取主窗口
|
||
widget = self.log_text
|
||
while widget and widget.master:
|
||
if isinstance(widget.master, tk.Tk):
|
||
# 找到主窗口,使用 after 调度更新
|
||
widget.master.after(0, update)
|
||
return
|
||
widget = widget.master
|
||
|
||
# 如果找不到主窗口,直接调用(适用于非 GUI 模式或测试)
|
||
update()
|
||
except Exception:
|
||
# 如果线程调度失败,直接调用
|
||
update()
|
||
|
||
except Exception:
|
||
# 处理错误,避免影响主程序
|
||
self.handleError(record)
|
||
|
||
def _strip_redundant_level_prefix(self, message: str) -> str:
|
||
"""
|
||
移除消息开头的冗余级别标记
|
||
|
||
例如:"[INFO] 读取 ProductionID 文件" -> "读取 ProductionID 文件"
|
||
"[ERROR] 错误信息" -> "错误信息"
|
||
|
||
Args:
|
||
message: 原始消息
|
||
|
||
Returns:
|
||
清理后的消息
|
||
"""
|
||
# 常见的日志级别标记模式
|
||
level_pattern = r"^\[(?:INFO|WARNING|ERROR|DEBUG|CRITICAL|WARN|SUCCESS)\]\s*"
|
||
match = re.match(level_pattern, message)
|
||
if match:
|
||
# 移除匹配到的级别前缀
|
||
return message[match.end() :]
|
||
return message
|