#!/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