feat: implement unified logging system for GUI components

Add centralized logging mechanism that simultaneously outputs to console
and GUI log components, improving code maintainability and consistency.

Changes:
- Add gui/log_config.py for centralized logging configuration
- Add gui/widgets/log_handler.py as bridge between logging and LogText
- Integrate unified logging into DataExtractionTab and MaterialValidationTab
- Initialize logging system in MainWindow on startup
- Improve error messages in material_status_validator for empty results
- Add documentation for logging mechanism and refactoring

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-02-24 20:31:59 +08:00
parent 3c45ef58d1
commit 24053c6a3b
9 changed files with 821 additions and 24 deletions

120
gui/widgets/log_handler.py Normal file
View File

@@ -0,0 +1,120 @@
#!/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