SXXXXXXX_PyMsc/pymsc/gui/docking/logger_dock.py

161 lines
5.8 KiB
Python

# -*- coding: utf-8 -*-
"""
Logger Dock: Real-time system logs display.
"""
import tkinter as tk
from tkinter import ttk, scrolledtext
import logging
from pymsc.gui.docking import DockFrame
class LoggerDock(DockFrame):
"""
Logger dockable panel with scrollable text display.
Shows real-time logs from the ARTOS system.
"""
def __init__(self, parent: tk.Widget, existing_widget=None, existing_handler=None, **kwargs):
"""
Args:
parent: Parent widget
existing_widget: Optional pre-created ScrolledText widget to use
existing_handler: Optional pre-created TextWidgetHandler to use
"""
self.existing_widget = existing_widget
self.existing_handler = existing_handler
super().__init__(
parent,
title="System Logger",
closable=False, # Logger always visible
**kwargs
)
self.log_handler = existing_handler
def populate_content(self):
"""Create or reuse the scrollable log text widget."""
if self.existing_widget:
# Reuse existing widget - reparent it properly
self.log_text = self.existing_widget
# Change the parent of the widget
self.log_text.master = self.content_frame
self.log_text.pack_forget() # Remove from old parent
# Re-pack in new parent
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# Handler already exists and is attached
# Just log that we've transferred
import logging
logger = logging.getLogger('ARTOS')
logger.info("System Logger dock created - continuing to capture logs")
else:
# Create new widget (fallback if called without existing widget)
self.log_text = scrolledtext.ScrolledText(
self.content_frame,
wrap=tk.WORD,
height=10,
font=('Consolas', 9),
bg='#ffffff',
fg='#000000',
state='disabled' # Read-only
)
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# Configure tags for different log levels
self.log_text.tag_config('INFO', foreground='#0066cc')
self.log_text.tag_config('WARNING', foreground='#ff8800')
self.log_text.tag_config('ERROR', foreground='#cc0000')
self.log_text.tag_config('DEBUG', foreground='#666666')
# Create custom log handler that writes to this widget
self.log_handler = TextWidgetHandler(self.log_text)
self.log_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
self.log_handler.setFormatter(formatter)
# Add handler to root logger
logging.getLogger().addHandler(self.log_handler)
# Add initial message
self.append_log("System Logger initialized. Ready to display logs.", 'INFO')
def _load_previous_logs(self):
"""Load and display logs from the log file that were written before GUI started."""
import os
log_file = 'logs/artos.log'
if not os.path.exists(log_file):
return
try:
with open(log_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
# Load last 100 lines to avoid overwhelming the widget
recent_lines = lines[-100:] if len(lines) > 100 else lines
self.log_text.config(state='normal')
for line in recent_lines:
line = line.rstrip('\n')
if not line:
continue
# Detect log level from line content
level = 'INFO'
if ' - ERROR - ' in line:
level = 'ERROR'
elif ' - WARNING - ' in line:
level = 'WARNING'
elif ' - DEBUG - ' in line:
level = 'DEBUG'
self.log_text.insert(tk.END, line + '\n', level)
self.log_text.see(tk.END)
self.log_text.config(state='disabled')
except Exception as e:
# Silently ignore if we can't read the file
pass
def append_log(self, message: str, level: str = 'INFO'):
"""
Append a log message to the text widget.
Args:
message: Log message text
level: Log level (INFO, WARNING, ERROR, DEBUG)
"""
self.log_text.config(state='normal')
self.log_text.insert(tk.END, message + '\n', level)
self.log_text.see(tk.END) # Auto-scroll to bottom
self.log_text.config(state='disabled')
class TextWidgetHandler(logging.Handler):
"""
Custom logging handler that outputs to a Tkinter Text widget.
"""
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
def emit(self, record):
"""Emit a log record to the text widget."""
try:
msg = self.format(record)
level = record.levelname
# Thread-safe update
self.text_widget.after(0, self._append_log, msg, level)
except Exception:
self.handleError(record)
def _append_log(self, message, level):
"""Append message to text widget (must be called from main thread)."""
self.text_widget.config(state='normal')
self.text_widget.insert(tk.END, message + '\n', level)
self.text_widget.see(tk.END)
self.text_widget.config(state='disabled')