Files
pqAutomationApp/app/views/pq_log_gui.py

160 lines
5.8 KiB
Python
Raw Normal View History

2026-04-21 15:31:48 +08:00
import threading
from datetime import datetime
2026-04-16 16:51:05 +08:00
import tkinter as tk
import ttkbootstrap as ttk
class PQLogGUI(ttk.Frame):
2026-04-21 15:31:48 +08:00
VALID_LEVELS = {"info", "success", "warning", "error", "debug", "separator", "blank"}
2026-04-16 16:51:05 +08:00
def __init__(self, parent):
super().__init__(parent)
2026-04-21 15:31:48 +08:00
self._line_count = 0
self._max_lines = 1500
2026-04-16 16:51:05 +08:00
self.create_widgets()
def create_widgets(self):
log_frame = ttk.LabelFrame(self, text="测试日志")
log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
2026-04-21 15:31:48 +08:00
toolbar = ttk.Frame(log_frame)
toolbar.pack(fill=tk.X, padx=6, pady=(6, 2))
self.log_summary_var = tk.StringVar(value="0 条日志")
ttk.Label(
toolbar,
textvariable=self.log_summary_var,
bootstyle="secondary",
).pack(side=tk.LEFT)
ttk.Button(
toolbar,
text="清空日志",
command=self.clear_log,
bootstyle="secondary-outline",
width=10,
).pack(side=tk.RIGHT)
text_container = ttk.Frame(log_frame)
text_container.pack(fill=tk.BOTH, expand=True, padx=6, pady=(0, 6))
self.log_text = tk.Text(
text_container,
height=10,
width=50,
wrap=tk.WORD,
font=("Consolas", 10),
bg="#fbfcfe",
fg="#1f2937",
relief=tk.FLAT,
bd=0,
padx=10,
pady=8,
spacing1=2,
spacing3=2,
insertbackground="#1f2937",
)
2026-04-16 16:51:05 +08:00
self.log_text.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)
2026-04-21 15:31:48 +08:00
log_scrollbar = ttk.Scrollbar(text_container, command=self.log_text.yview)
2026-04-16 16:51:05 +08:00
log_scrollbar.pack(fill=tk.Y, side=tk.RIGHT)
self.log_text.config(yscrollcommand=log_scrollbar.set)
2026-04-21 15:31:48 +08:00
self._configure_tags()
2026-04-16 16:51:05 +08:00
self.log_text.config(state=tk.DISABLED)
2026-04-21 15:31:48 +08:00
def log(self, message, level="info"):
if threading.current_thread() is not threading.main_thread():
self.after(0, self.log, message, level)
return
text = "" if message is None else str(message)
normalized_level = self._normalize_level(level, text)
2026-04-16 16:51:05 +08:00
self.log_text.config(state=tk.NORMAL)
2026-04-21 15:31:48 +08:00
self._append_message(text, normalized_level)
2026-04-16 16:51:05 +08:00
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def clear_log(self):
2026-04-21 15:31:48 +08:00
if threading.current_thread() is not threading.main_thread():
self.after(0, self.clear_log)
return
2026-04-16 16:51:05 +08:00
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
2026-04-21 15:31:48 +08:00
self.log_text.config(state=tk.DISABLED)
self._line_count = 0
self._update_summary()
def _configure_tags(self):
self.log_text.tag_configure("timestamp", foreground="#6b7280")
self.log_text.tag_configure("level_info", foreground="#2563eb")
self.log_text.tag_configure("level_success", foreground="#0f766e")
self.log_text.tag_configure("level_warning", foreground="#b45309")
self.log_text.tag_configure("level_error", foreground="#b91c1c")
self.log_text.tag_configure("level_debug", foreground="#7c3aed")
self.log_text.tag_configure("message", foreground="#1f2937")
self.log_text.tag_configure("message_success", foreground="#0f766e")
self.log_text.tag_configure("message_warning", foreground="#b45309")
self.log_text.tag_configure("message_error", foreground="#991b1b")
self.log_text.tag_configure("message_debug", foreground="#6d28d9")
self.log_text.tag_configure("separator", foreground="#94a3b8")
self.log_text.tag_configure("traceback", foreground="#7f1d1d")
self.log_text.tag_configure("blank", spacing1=4, spacing3=4)
def _append_message(self, message, level):
lines = message.splitlines() or [""]
for line in lines:
self._append_line(line, level)
self._trim_excess_lines()
self._update_summary()
def _append_line(self, line, level):
timestamp = datetime.now().strftime("%H:%M:%S")
rendered = "" if line is None else str(line).strip()
if level == "blank" or not rendered:
self.log_text.insert(tk.END, "\n", ("blank",))
self._line_count += 1
return
if level == "separator":
self.log_text.insert(tk.END, f"[{timestamp}] ", ("timestamp",))
self.log_text.insert(tk.END, "[SECTION] ", ("level_info",))
self.log_text.insert(tk.END, rendered + "\n", ("separator",))
self._line_count += 1
return
level_tag = f"level_{level}"
level_label = level.upper().ljust(7)
if level == "error" and rendered.startswith("Traceback"):
message_tag = "traceback"
elif level in {"success", "warning", "error", "debug"}:
message_tag = f"message_{level}"
else:
message_tag = "message"
self.log_text.insert(tk.END, f"[{timestamp}] ", ("timestamp",))
self.log_text.insert(tk.END, f"[{level_label}] ", (level_tag,))
self.log_text.insert(tk.END, rendered + "\n", (message_tag,))
self._line_count += 1
def _normalize_level(self, level, message):
normalized = "info" if level is None else str(level).strip().lower()
if normalized not in self.VALID_LEVELS:
normalized = "info"
if normalized == "info" and (message is None or str(message).strip() == ""):
return "blank"
return normalized
def _trim_excess_lines(self):
overflow = self._line_count - self._max_lines
if overflow <= 0:
return
self.log_text.delete("1.0", f"{overflow + 1}.0")
self._line_count = self._max_lines
def _update_summary(self):
self.log_summary_var.set(f"{self._line_count} 条日志")