-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp_logger.py
More file actions
106 lines (89 loc) · 3.77 KB
/
app_logger.py
File metadata and controls
106 lines (89 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# app_logger.py
import tkinter as tk
from datetime import datetime
from queue import Queue
import threading
import time
class AppLogger:
"""
一个线程安全的日志记录器类,用于将带有时间戳的日志消息
安全地显示在 Tkinter 的 tk.Text 组件中,并支持自动滚动。
"""
def __init__(self, text_widget: tk.Text, root: tk.Tk):
"""
初始化日志记录器。
Args:
text_widget (tk.Text): 用于显示日志的 Tkinter Text 组件。
root (tk.Tk): Tkinter根窗口对象,用于 post/after 调用。
"""
self.text_widget = text_widget
self.root = root
self.log_queue = Queue()
self.running = True
self.processor_thread = threading.Thread(target=self._process_logs, daemon=True)
self.processor_thread.start()
# 调试语句
print("DEBUG: AppLogger 初始化并启动日志处理线程。")
# 配置 Text 组件的 Tag,用于日志级别的高亮
text_widget.tag_config('INFO', foreground='black')
text_widget.tag_config('WARNING', foreground='#FF8C00') # 橙色
text_widget.tag_config('ERROR', foreground='red', font=('TkDefaultFont', 9, 'bold'))
text_widget.tag_config('DEBUG', foreground='gray')
def log(self, message: str, level: str = 'INFO'):
"""
将日志消息添加到队列中,等待处理线程安全地更新 GUI。
Args:
message (str): 日志内容。
level (str): 日志级别,可以是 'INFO', 'WARNING', 'ERROR', 'DEBUG'。
"""
timestamp = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
log_entry = (timestamp, message, level.upper())
self.log_queue.put(log_entry)
def _process_logs(self):
"""
后台线程循环,从队列中取出日志消息并调用 GUI 更新方法。
"""
while self.running:
try:
# 阻塞直到队列中有项目,timeout防止程序退出时阻塞
timestamp, message, level = self.log_queue.get(timeout=0.1)
# 线程安全地调用 Tkinter GUI 更新方法
self.root.after(0, self._update_gui_log, timestamp, message, level)
self.log_queue.task_done()
except Exception as e:
# 忽略队列超时的异常
if 'Queue Empty' not in str(e):
# 调试语句
# print(f"WARNING: 日志处理线程异常: {e}")
pass
def _update_gui_log(self, timestamp: str, message: str, level: str):
"""
在 Tkinter 主线程中执行,安全地更新 Text 组件。
Args:
timestamp (str): 日志时间戳。
message (str): 日志内容。
level (str): 日志级别。
"""
# 格式化完整的日志行
log_line = f"{timestamp} [{level}] {message}\n"
# 插入文本
self.text_widget.insert(tk.END, log_line, level) # 使用 level 作为 tag
# 自动滚动到最底部
self.text_widget.see(tk.END)
# 限制日志行数(可选:防止内存占用过大,这里暂时不限制)
# line_count = int(self.text_widget.index('end-1c').split('.')[0])
# max_lines = 1000
# if line_count > max_lines:
# self.text_widget.delete('1.0', f'{line_count - max_lines + 1}.0')
def stop(self):
"""
安全地停止日志处理线程。
"""
self.running = False
self.processor_thread.join(timeout=1)
# 调试语句
print("DEBUG: AppLogger 线程已停止。")
# 确保在程序退出时调用 stop(),防止线程泄漏
def atexit_handler(logger):
if logger:
logger.stop()