# target_simulator/gui/logger_panel.py """ A small Toplevel UI to inspect and change logger levels at runtime. """ import tkinter as tk from tkinter import ttk, messagebox import logging from typing import List LEVELS = [ ("NOTSET", logging.NOTSET), ("DEBUG", logging.DEBUG), ("INFO", logging.INFO), ("WARNING", logging.WARNING), ("ERROR", logging.ERROR), ("CRITICAL", logging.CRITICAL), ] class LoggerPanel(tk.Toplevel): """Toplevel window that allows setting logger levels at runtime.""" def __init__(self, master=None): super().__init__(master) self.title("Logger Levels") self.geometry("520x420") self.transient(master) self.grab_set() self.logger_names = [] # type: List[str] self._create_widgets() self._populate_logger_list() def _create_widgets(self): top = ttk.Frame(self) top.pack(fill=tk.BOTH, expand=True, padx=8, pady=8) # Left: list of logger names left = ttk.Frame(top) left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) ttk.Label(left, text="Available loggers:").pack(anchor=tk.W) self.logger_listbox = tk.Listbox(left, exportselection=False) self.logger_listbox.pack(fill=tk.BOTH, expand=True, padx=(0, 6), pady=(4, 0)) self.logger_listbox.bind("<>", self._on_select_logger) # Right: controls right = ttk.Frame(top) right.pack(side=tk.RIGHT, fill=tk.Y) ttk.Label(right, text="Selected logger:").pack(anchor=tk.W) self.selected_name_var = tk.StringVar(value="") ttk.Label(right, textvariable=self.selected_name_var, foreground="blue").pack(anchor=tk.W, pady=(0, 6)) ttk.Label(right, text="Level:").pack(anchor=tk.W) self.level_var = tk.StringVar(value="INFO") level_names = [n for n, v in LEVELS] self.level_combo = ttk.Combobox(right, values=level_names, textvariable=self.level_var, state="readonly", width=12) self.level_combo.pack(anchor=tk.W, pady=(0, 6)) ttk.Button(right, text="Apply", command=self._apply_level).pack(fill=tk.X, pady=(6, 4)) ttk.Button(right, text="Reset to NOTSET", command=self._reset_level).pack(fill=tk.X) ttk.Separator(self).pack(fill=tk.X, pady=6) bottom = ttk.Frame(self) bottom.pack(fill=tk.X, padx=8, pady=6) ttk.Label(bottom, text="Add / open logger by name:").pack(anchor=tk.W) self.new_logger_var = tk.StringVar() entry = ttk.Entry(bottom, textvariable=self.new_logger_var) entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 6)) ttk.Button(bottom, text="Open", command=self._open_named_logger).pack(side=tk.LEFT) ttk.Button(self, text="Refresh", command=self._populate_logger_list).pack(side=tk.RIGHT, padx=8, pady=(0, 8)) ttk.Button(self, text="Close", command=self._on_close).pack(side=tk.RIGHT, pady=(0, 8)) def _gather_logger_names(self) -> List[str]: # Gather logger names from the logging manager plus some defaults manager = logging.root.manager names = list(getattr(manager, 'loggerDict', {}).keys()) # Add a few commonly useful module names if missing defaults = [ 'target_simulator', 'target_simulator.analysis.simulation_state_hub', 'target_simulator.gui.sfp_debug_window', 'target_simulator.gui.ppi_display', 'target_simulator.gui.payload_router', 'target_simulator.core.sfp_transport', ] for d in defaults: if d not in names: names.append(d) names = sorted(set(names)) return names def _populate_logger_list(self): self.logger_listbox.delete(0, tk.END) self.logger_names = self._gather_logger_names() for n in self.logger_names: self.logger_listbox.insert(tk.END, n) def _on_select_logger(self, event=None): sel = self.logger_listbox.curselection() if not sel: return idx = sel[0] name = self.logger_names[idx] self.selected_name_var.set(name) lg = logging.getLogger(name) lvl = lg.getEffectiveLevel() # Translate to name lvl_name = logging.getLevelName(lvl) self.level_var.set(lvl_name) def _apply_level(self): name = self.selected_name_var.get() if not name: messagebox.showwarning("No logger selected", "Select a logger from the list first.") return lvl_name = self.level_var.get() lvl = next((v for n, v in LEVELS if n == lvl_name), logging.INFO) logging.getLogger(name).setLevel(lvl) messagebox.showinfo("Logger level set", f"Logger '{name}' set to {lvl_name}.") def _reset_level(self): name = self.selected_name_var.get() if not name: messagebox.showwarning("No logger selected", "Select a logger from the list first.") return logging.getLogger(name).setLevel(logging.NOTSET) self.level_var.set('NOTSET') messagebox.showinfo("Logger reset", f"Logger '{name}' reset to NOTSET.") def _open_named_logger(self): name = self.new_logger_var.get().strip() if not name: return # If exists in list, select it try: idx = self.logger_names.index(name) except ValueError: # Add to list self.logger_names.append(name) self.logger_names.sort() self._populate_logger_list() idx = self.logger_names.index(name) self.logger_listbox.select_clear(0, tk.END) self.logger_listbox.select_set(idx) self.logger_listbox.see(idx) self._on_select_logger() def _on_close(self): try: self.grab_release() except Exception: pass self.destroy()