aggiunta schermata per configurazione logger con salvataggio preferenze

This commit is contained in:
VALLONGOL 2025-10-28 12:45:04 +01:00
parent 3bfa5edf88
commit b3a79d44f1
8 changed files with 570 additions and 20 deletions

3
logger_prefs.json Normal file
View File

@ -0,0 +1,3 @@
{
"saved_levels": {}
}

View File

@ -3,7 +3,7 @@
"scan_limit": 60, "scan_limit": 60,
"max_range": 100, "max_range": 100,
"geometry": "1599x1089+501+84", "geometry": "1599x1089+501+84",
"last_selected_scenario": "scenario3", "last_selected_scenario": null,
"connection": { "connection": {
"target": { "target": {
"type": "sfp", "type": "sfp",

View File

@ -16,9 +16,14 @@ if project_root not in sys.path:
sys.path.insert(0, project_root) sys.path.insert(0, project_root)
from target_simulator.gui.main_view import MainView from target_simulator.gui.main_view import MainView
from utils.logger import setup_basic_logging, get_logger, add_tkinter_handler from target_simulator.utils.logger import (
setup_basic_logging,
get_logger,
add_tkinter_handler,
apply_saved_logger_levels,
)
from config import LOGGING_CONFIG from target_simulator.config import LOGGING_CONFIG
def main(): def main():
@ -32,6 +37,13 @@ def main():
# Setup the global logging system, connecting it to the GUI's main loop # Setup the global logging system, connecting it to the GUI's main loop
setup_basic_logging(app, LOGGING_CONFIG) setup_basic_logging(app, LOGGING_CONFIG)
# Apply any saved logger levels so preferences take effect even if the
# LoggerPanel is never opened.
try:
apply_saved_logger_levels()
except Exception:
pass
# Now that the logging system is active, add the handler for the GUI widget # Now that the logging system is active, add the handler for the GUI widget
add_tkinter_handler(app.log_text_widget, LOGGING_CONFIG) add_tkinter_handler(app.log_text_widget, LOGGING_CONFIG)

View File

@ -6,6 +6,9 @@ import tkinter as tk
from tkinter import ttk, messagebox from tkinter import ttk, messagebox
import logging import logging
from typing import List from typing import List
from target_simulator.utils.config_manager import ConfigManager
import os
import json
LEVELS = [ LEVELS = [
("NOTSET", logging.NOTSET), ("NOTSET", logging.NOTSET),
@ -29,17 +32,44 @@ class LoggerPanel(tk.Toplevel):
self.logger_names = [] # type: List[str] self.logger_names = [] # type: List[str]
# Config manager for persistence
try:
self._cfg = ConfigManager()
except Exception:
self._cfg = None
self._create_widgets() self._create_widgets()
self._populate_logger_list() self._populate_logger_list()
# Load persisted selections and levels
try:
self._load_persisted()
except Exception:
pass
def _create_widgets(self): def _create_widgets(self):
top = ttk.Frame(self) top = ttk.Frame(self)
top.pack(fill=tk.BOTH, expand=True, padx=8, pady=8) top.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
# Left: list of logger names # Left: filter + list of logger names
left = ttk.Frame(top) left = ttk.Frame(top)
left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
ttk.Label(left, text="Filter:").pack(anchor=tk.W)
self.filter_var = tk.StringVar(value="")
self.filter_entry = ttk.Entry(left, textvariable=self.filter_var)
self.filter_entry.pack(fill=tk.X, padx=(0, 6), pady=(2, 4))
# Call _on_filter_change on text changes
try:
# Python 3.6+ trace_add preferred
self.filter_var.trace_add("write", lambda *args: self._on_filter_change())
except Exception:
# Fallback for older trace
try:
self.filter_var.trace("w", lambda *args: self._on_filter_change())
except Exception:
pass
ttk.Label(left, text="Available loggers:").pack(anchor=tk.W) ttk.Label(left, text="Available loggers:").pack(anchor=tk.W)
self.logger_listbox = tk.Listbox(left, exportselection=False) 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.pack(fill=tk.BOTH, expand=True, padx=(0, 6), pady=(4, 0))
@ -61,6 +91,7 @@ class LoggerPanel(tk.Toplevel):
ttk.Button(right, text="Apply", command=self._apply_level).pack(fill=tk.X, pady=(6, 4)) 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.Button(right, text="Reset to NOTSET", command=self._reset_level).pack(fill=tk.X)
ttk.Button(right, text="Restore defaults", command=self._restore_defaults).pack(fill=tk.X, pady=(6, 0))
ttk.Separator(self).pack(fill=tk.X, pady=6) ttk.Separator(self).pack(fill=tk.X, pady=6)
@ -74,6 +105,7 @@ class LoggerPanel(tk.Toplevel):
ttk.Button(bottom, text="Open", command=self._open_named_logger).pack(side=tk.LEFT) 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="Refresh", command=self._populate_logger_list).pack(side=tk.RIGHT, padx=8, pady=(0, 8))
ttk.Button(self, text="Export", command=self._export_prefs).pack(side=tk.RIGHT, padx=(0,8), pady=(0,8))
ttk.Button(self, text="Close", command=self._on_close).pack(side=tk.RIGHT, 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]: def _gather_logger_names(self) -> List[str]:
@ -95,12 +127,71 @@ class LoggerPanel(tk.Toplevel):
names = sorted(set(names)) names = sorted(set(names))
return names return names
def _populate_logger_list(self): def _refresh_list(self):
"""Refresh listbox contents applying current filter against _all_logger_names."""
try:
filt = (self.filter_var.get() or "").strip().lower()
except Exception:
filt = ""
# Build filtered list
if hasattr(self, "_all_logger_names") and self._all_logger_names:
source = self._all_logger_names
else:
source = self.logger_names
if filt:
filtered = [n for n in source if filt in n.lower()]
else:
filtered = list(source)
# Remember current selection name to try to preserve it
try:
sel_idx = self.logger_listbox.curselection()
sel_name = self.logger_names[sel_idx[0]] if sel_idx else None
except Exception:
sel_name = None
# Update listbox
self.logger_listbox.delete(0, tk.END) self.logger_listbox.delete(0, tk.END)
self.logger_names = self._gather_logger_names() self.logger_names = sorted(filtered)
for n in self.logger_names: for n in self.logger_names:
self.logger_listbox.insert(tk.END, n) self.logger_listbox.insert(tk.END, n)
# Restore selection if still present
if sel_name and sel_name in self.logger_names:
idx = self.logger_names.index(sel_name)
self.logger_listbox.select_set(idx)
self.logger_listbox.see(idx)
self._on_select_logger()
def _on_filter_change(self, *args):
"""Callback for filter text changes; refresh the list."""
try:
self._refresh_list()
except Exception:
pass
def _populate_logger_list(self):
self.logger_listbox.delete(0, tk.END)
# Gather and cache the full set of logger names, then refresh list
self._all_logger_names = self._gather_logger_names()
# Apply filter and populate listbox
self._refresh_list()
# If user previously selected a logger, select it
try:
if self._cfg:
gen = self._cfg.get_general_settings()
lp = gen.get("logger_panel", {}) if isinstance(gen, dict) else {}
last = lp.get("last_selected")
if last and last in self.logger_names:
idx = self.logger_names.index(last)
self.logger_listbox.select_set(idx)
self.logger_listbox.see(idx)
self._on_select_logger()
except Exception:
pass
def _on_select_logger(self, event=None): def _on_select_logger(self, event=None):
sel = self.logger_listbox.curselection() sel = self.logger_listbox.curselection()
if not sel: if not sel:
@ -122,7 +213,41 @@ class LoggerPanel(tk.Toplevel):
lvl_name = self.level_var.get() lvl_name = self.level_var.get()
lvl = next((v for n, v in LEVELS if n == lvl_name), logging.INFO) lvl = next((v for n, v in LEVELS if n == lvl_name), logging.INFO)
logging.getLogger(name).setLevel(lvl) logging.getLogger(name).setLevel(lvl)
# Persist this selection into dedicated logger_prefs.json (single source of truth)
try:
cfg_path = getattr(self._cfg, 'filepath', None) if self._cfg else None
prefs_path = os.path.join(os.path.dirname(cfg_path), 'logger_prefs.json') if cfg_path else os.path.join(os.getcwd(), 'logger_prefs.json')
# Read existing prefs
try:
with open(prefs_path, 'r', encoding='utf-8') as f:
prefs = json.load(f) if f else {}
if not isinstance(prefs, dict):
prefs = {}
except Exception:
prefs = {}
saved = prefs.get('saved_levels', {}) if isinstance(prefs.get('saved_levels', {}), dict) else {}
saved[name] = lvl_name
prefs['saved_levels'] = saved
prefs['last_selected'] = name
with open(prefs_path, 'w', encoding='utf-8') as f:
json.dump(prefs, f, indent=4)
except Exception:
pass
# Post a status message to the application log instead of a modal dialog
try:
# Show a transient status in the main UI if available
parent = getattr(self, 'master', None)
if parent and hasattr(parent, 'show_status_message'):
parent.show_status_message(f"Logger '{name}' set to {lvl_name} (saved)")
else:
logging.getLogger(__name__).info("Logger '%s' set to %s (persisted)", name, lvl_name)
except Exception:
try:
messagebox.showinfo("Logger level set", f"Logger '{name}' set to {lvl_name}.") messagebox.showinfo("Logger level set", f"Logger '{name}' set to {lvl_name}.")
except Exception:
pass
def _reset_level(self): def _reset_level(self):
name = self.selected_name_var.get() name = self.selected_name_var.get()
@ -131,7 +256,41 @@ class LoggerPanel(tk.Toplevel):
return return
logging.getLogger(name).setLevel(logging.NOTSET) logging.getLogger(name).setLevel(logging.NOTSET)
self.level_var.set('NOTSET') self.level_var.set('NOTSET')
# Remove persisted level for this logger from logger_prefs.json
try:
cfg_path = getattr(self._cfg, 'filepath', None) if self._cfg else None
prefs_path = os.path.join(os.path.dirname(cfg_path), 'logger_prefs.json') if cfg_path else os.path.join(os.getcwd(), 'logger_prefs.json')
try:
with open(prefs_path, 'r', encoding='utf-8') as f:
prefs = json.load(f) if f else {}
if not isinstance(prefs, dict):
prefs = {}
except Exception:
prefs = {}
saved = prefs.get('saved_levels', {}) if isinstance(prefs.get('saved_levels', {}), dict) else {}
if name in saved:
del saved[name]
prefs['saved_levels'] = saved
# if last_selected pointed to this logger, remove it
if prefs.get('last_selected') == name:
prefs.pop('last_selected', None)
with open(prefs_path, 'w', encoding='utf-8') as f:
json.dump(prefs, f, indent=4)
except Exception:
pass
try:
parent = getattr(self, 'master', None)
if parent and hasattr(parent, 'show_status_message'):
parent.show_status_message(f"Logger '{name}' reset to NOTSET (saved)")
else:
logging.getLogger(__name__).info("Logger '%s' reset to NOTSET (persisted)", name)
except Exception:
try:
messagebox.showinfo("Logger reset", f"Logger '{name}' reset to NOTSET.") messagebox.showinfo("Logger reset", f"Logger '{name}' reset to NOTSET.")
except Exception:
pass
def _open_named_logger(self): def _open_named_logger(self):
name = self.new_logger_var.get().strip() name = self.new_logger_var.get().strip()
@ -139,17 +298,116 @@ class LoggerPanel(tk.Toplevel):
return return
# If exists in list, select it # If exists in list, select it
try: try:
idx = self.logger_names.index(name) idx = self._all_logger_names.index(name)
except ValueError: except ValueError:
# Add to list # Add to list
self.logger_names.append(name) # Add to cached master list and refresh
self.logger_names.sort() if not hasattr(self, "_all_logger_names"):
self._all_logger_names = []
if name not in self._all_logger_names:
self._all_logger_names.append(name)
self._all_logger_names.sort()
self._populate_logger_list() self._populate_logger_list()
idx = self.logger_names.index(name) idx = self.logger_names.index(name)
self.logger_listbox.select_clear(0, tk.END) self.logger_listbox.select_clear(0, tk.END)
self.logger_listbox.select_set(idx) self.logger_listbox.select_set(idx)
self.logger_listbox.see(idx) self.logger_listbox.see(idx)
self._on_select_logger() self._on_select_logger()
# Persist last selected into logger_prefs.json
try:
cfg_path = getattr(self._cfg, 'filepath', None) if self._cfg else None
prefs_path = os.path.join(os.path.dirname(cfg_path), 'logger_prefs.json') if cfg_path else os.path.join(os.getcwd(), 'logger_prefs.json')
try:
with open(prefs_path, 'r', encoding='utf-8') as f:
prefs = json.load(f) if f else {}
if not isinstance(prefs, dict):
prefs = {}
except Exception:
prefs = {}
prefs['last_selected'] = name
with open(prefs_path, 'w', encoding='utf-8') as f:
json.dump(prefs, f, indent=4)
except Exception:
pass
try:
parent = getattr(self, 'master', None)
if parent and hasattr(parent, 'show_status_message'):
parent.show_status_message(f"Selected logger: {name}")
except Exception:
pass
def _restore_defaults(self):
"""Clear all saved logger preferences and reset those loggers to NOTSET."""
if not self._cfg:
try:
messagebox.showinfo("Restore defaults", "Could not access settings to restore defaults.")
except Exception:
pass
return
try:
gen = self._cfg.get_general_settings()
lp = gen.get("logger_panel", {}) if isinstance(gen, dict) else {}
saved = lp.get("saved_levels", {}) if isinstance(lp, dict) else {}
# Reset each saved logger to NOTSET
for name in list(saved.keys()):
try:
logging.getLogger(name).setLevel(logging.NOTSET)
except Exception:
pass
# Remove saved_levels
# Clear dedicated prefs file instead of touching settings.json
cfg_path = getattr(self._cfg, 'filepath', None) if self._cfg else None
prefs_path = os.path.join(os.path.dirname(cfg_path), 'logger_prefs.json') if cfg_path else os.path.join(os.getcwd(), 'logger_prefs.json')
try:
with open(prefs_path, 'w', encoding='utf-8') as f:
json.dump({"saved_levels": {}}, f, indent=4)
except Exception:
# fallback: attempt to remove the file
try:
if os.path.exists(prefs_path):
os.remove(prefs_path)
except Exception:
pass
logging.getLogger(__name__).info("LoggerPanel: restored defaults and cleared saved preferences")
# Refresh the list and selection
self._populate_logger_list()
except Exception:
try:
messagebox.showinfo("Restore defaults", "Failed to restore defaults.")
except Exception:
pass
def _export_prefs(self):
"""Export saved logger preferences to a separate JSON file for inspection."""
if not self._cfg:
try:
messagebox.showinfo("Export prefs", "Settings manager not available; cannot export.")
except Exception:
pass
return
try:
gen = self._cfg.get_general_settings()
lp = gen.get("logger_panel", {}) if isinstance(gen, dict) else {}
saved = lp.get("saved_levels", {}) if isinstance(lp, dict) else {}
# Determine export path next to settings.json
cfg_path = getattr(self._cfg, 'filepath', None)
export_dir = os.path.dirname(cfg_path) if cfg_path else os.getcwd()
export_path = os.path.join(export_dir, "logger_prefs.json")
with open(export_path, 'w', encoding='utf-8') as f:
json.dump({"saved_levels": saved}, f, indent=4)
parent = getattr(self, 'master', None)
if parent and hasattr(parent, 'show_status_message'):
parent.show_status_message(f"Exported logger prefs to {export_path}")
else:
logging.getLogger(__name__).info("Exported logger prefs to %s", export_path)
except Exception:
try:
messagebox.showinfo("Export prefs", "Failed to export logger preferences.")
except Exception:
pass
def _on_close(self): def _on_close(self):
try: try:
@ -157,3 +415,37 @@ class LoggerPanel(tk.Toplevel):
except Exception: except Exception:
pass pass
self.destroy() self.destroy()
def _load_persisted(self):
"""Load persisted logger panel settings and apply saved levels."""
# Prefer reading the dedicated prefs file
cfg_path = getattr(self._cfg, 'filepath', None) if self._cfg else None
prefs_path = os.path.join(os.path.dirname(cfg_path), 'logger_prefs.json') if cfg_path else os.path.join(os.getcwd(), 'logger_prefs.json')
saved = {}
try:
if os.path.exists(prefs_path):
with open(prefs_path, 'r', encoding='utf-8') as f:
jp = json.load(f)
if isinstance(jp, dict):
saved = jp.get('saved_levels', {}) or {}
last = jp.get('last_selected')
if last:
# store last_selected in prefs for later UI use
pass
else:
# Fallback to settings.json
if self._cfg:
gen = self._cfg.get_general_settings()
lp = gen.get("logger_panel", {}) if isinstance(gen, dict) else {}
saved = lp.get("saved_levels", {}) if isinstance(lp, dict) else {}
except Exception:
saved = {}
# Apply saved levels to loggers
for name, lvl_name in (saved or {}).items():
try:
lvl = logging.getLevelName(lvl_name)
if isinstance(lvl, int):
logging.getLogger(name).setLevel(lvl)
except Exception:
pass

View File

@ -87,6 +87,8 @@ class MainView(tk.Tk):
self._create_menubar() self._create_menubar()
self._create_main_layout() self._create_main_layout()
self._create_statusbar() self._create_statusbar()
# Id for scheduled status clear; used by show_status_message
self._status_after_id = None
# --- Post-UI Initialization --- # --- Post-UI Initialization ---
self._initialize_communicators() self._initialize_communicators()
@ -383,6 +385,36 @@ class MainView(tk.Tk):
side=tk.LEFT, fill=tk.X, expand=True, padx=5 side=tk.LEFT, fill=tk.X, expand=True, padx=5
) )
def show_status_message(self, text: str, timeout_ms: int = 3000):
"""Show a transient status message in the main status bar.
If another message is scheduled to clear, cancel it and schedule the
new message to be cleared after timeout_ms.
"""
try:
# Cancel previous scheduled clear if any
try:
if self._status_after_id is not None:
self.after_cancel(self._status_after_id)
except Exception:
pass
# Set message
self.status_var.set(text)
# Schedule clear back to Ready
def _clear():
try:
self.status_var.set("Ready")
except Exception:
pass
self._status_after_id = self.after(timeout_ms, _clear)
except Exception:
# As a fallback, log the status
try:
self.logger.info(text)
except Exception:
pass
def _draw_status_indicator(self, canvas, color): def _draw_status_indicator(self, canvas, color):
canvas.delete("all") canvas.delete("all")
canvas.create_oval(2, 2, 14, 14, fill=color, outline="black") canvas.create_oval(2, 2, 14, 14, fill=color, outline="black")
@ -1116,14 +1148,44 @@ class MainView(tk.Tk):
if self.is_simulation_running.get(): if self.is_simulation_running.get():
self._on_stop_simulation() self._on_stop_simulation()
settings_to_save = { # Merge current runtime general settings with any existing saved
# settings so we don't clobber unrelated keys (e.g., logger_panel).
try:
existing = self.config_manager.get_general_settings() or {}
except Exception:
existing = {}
settings_to_save = dict(existing)
settings_to_save.update(
{
"scan_limit": self.scan_limit, "scan_limit": self.scan_limit,
"max_range": self.max_range, "max_range": self.max_range,
"geometry": self.winfo_geometry(), "geometry": self.winfo_geometry(),
"last_selected_scenario": self.current_scenario_name, "last_selected_scenario": self.current_scenario_name,
} }
)
# Save merged general settings and connection settings separately
try:
self.config_manager.save_general_settings(settings_to_save) self.config_manager.save_general_settings(settings_to_save)
except Exception:
# Fallback: try to write the minimal dict if merge/save fails
try:
self.config_manager.save_general_settings(
{
"scan_limit": self.scan_limit,
"max_range": self.max_range,
"geometry": self.winfo_geometry(),
"last_selected_scenario": self.current_scenario_name,
}
)
except Exception:
pass
try:
self.config_manager.save_connection_settings(self.connection_config) self.config_manager.save_connection_settings(self.connection_config)
except Exception:
pass
if self.target_communicator: if self.target_communicator:
if hasattr(self.target_communicator, 'remove_connection_state_callback'): if hasattr(self.target_communicator, 'remove_connection_state_callback'):

View File

@ -149,7 +149,21 @@ class ConfigManager:
def save_general_settings(self, data: Dict[str, Any]): def save_general_settings(self, data: Dict[str, Any]):
"""Saves the general settings.""" """Saves the general settings."""
self._settings["general"] = data # Merge incoming general settings with existing ones to avoid
# overwriting unrelated keys (for example: logger_panel saved prefs).
existing = self._settings.get("general", {}) if isinstance(self._settings, dict) else {}
try:
# Shallow merge: if a key contains a dict, update its contents
for k, v in (data or {}).items():
if isinstance(v, dict) and k in existing and isinstance(existing.get(k), dict):
existing[k].update(v)
else:
existing[k] = v
except Exception:
# Fallback: replace entirely if merge fails
existing = data
self._settings["general"] = existing
self._save_settings() self._save_settings()
def get_scenario_names(self) -> List[str]: def get_scenario_names(self) -> List[str]:

View File

@ -7,6 +7,9 @@ from queue import Queue, Empty as QueueEmpty
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from contextlib import contextmanager from contextlib import contextmanager
from logging import Logger from logging import Logger
from target_simulator.utils.config_manager import ConfigManager
import os
import json
# Module-level logger for utils.logging helpers # Module-level logger for utils.logging helpers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -210,3 +213,50 @@ def shutdown_logging_system():
# Final flush of the queue # Final flush of the queue
_process_global_log_queue() _process_global_log_queue()
logging.shutdown() logging.shutdown()
def apply_saved_logger_levels():
"""Apply saved logger levels from ConfigManager at startup.
Reads `general.logger_panel.saved_levels` from settings.json and sets
each configured logger to the saved level name.
"""
try:
cfg = ConfigManager()
except Exception:
return
try:
# Prefer a dedicated logger_prefs.json next to the main settings file
prefs_path = None
cfg_path = getattr(cfg, "filepath", None)
if cfg_path:
prefs_path = os.path.join(os.path.dirname(cfg_path), "logger_prefs.json")
saved = {}
if prefs_path and os.path.exists(prefs_path):
try:
with open(prefs_path, "r", encoding="utf-8") as f:
jp = json.load(f)
if isinstance(jp, dict):
saved = jp.get("saved_levels", {}) or {}
except Exception:
saved = {}
else:
# Fallback to settings.json general.logger_panel
try:
gen = cfg.get_general_settings()
lp = gen.get("logger_panel", {}) if isinstance(gen, dict) else {}
saved = lp.get("saved_levels", {}) if isinstance(lp, dict) else {}
except Exception:
saved = {}
for name, lvl_name in (saved or {}).items():
try:
lvl_val = logging.getLevelName(lvl_name)
if isinstance(lvl_val, int):
logging.getLogger(name).setLevel(lvl_val)
except Exception:
pass
except Exception:
pass

View File

@ -0,0 +1,117 @@
import os
import json
import logging
from target_simulator.utils.config_manager import ConfigManager
from target_simulator.utils.logger import apply_saved_logger_levels
def _prefs_path(cfg: ConfigManager) -> str:
return os.path.join(os.path.dirname(cfg.filepath), "logger_prefs.json")
def test_apply_saved_logger_levels_from_prefs_file(tmp_path):
cfg = ConfigManager()
settings_path = cfg.filepath
prefs = {"saved_levels": {"test.logger.persistence": "DEBUG"}}
prefs_path = _prefs_path(cfg)
# Backup existing settings.json and prefs if present
if os.path.exists(settings_path):
with open(settings_path, "r", encoding="utf-8") as f:
orig_settings = f.read()
else:
orig_settings = None
if os.path.exists(prefs_path):
with open(prefs_path, "r", encoding="utf-8") as f:
orig_prefs = f.read()
else:
orig_prefs = None
try:
# Ensure the logger starts at INFO
lg = logging.getLogger("test.logger.persistence")
lg.setLevel(logging.INFO)
# Write prefs file and apply
with open(prefs_path, "w", encoding="utf-8") as f:
json.dump(prefs, f, indent=2)
apply_saved_logger_levels()
# After applying, the logger should be DEBUG
assert logging.getLogger("test.logger.persistence").level == logging.DEBUG
# Now change prefs to WARNING and re-apply
prefs["saved_levels"]["test.logger.persistence"] = "WARNING"
with open(prefs_path, "w", encoding="utf-8") as f:
json.dump(prefs, f, indent=2)
apply_saved_logger_levels()
assert logging.getLogger("test.logger.persistence").level == logging.WARNING
finally:
# Restore original files
if orig_settings is not None:
with open(settings_path, "w", encoding="utf-8") as f:
f.write(orig_settings)
if orig_prefs is not None:
with open(prefs_path, "w", encoding="utf-8") as f:
f.write(orig_prefs)
else:
try:
if os.path.exists(prefs_path):
os.remove(prefs_path)
except Exception:
pass
def test_apply_saved_logger_levels_fallback_to_settings(tmp_path):
cfg = ConfigManager()
settings_path = cfg.filepath
prefs_path = _prefs_path(cfg)
# Backup
if os.path.exists(settings_path):
with open(settings_path, "r", encoding="utf-8") as f:
orig_settings = f.read()
else:
orig_settings = None
if os.path.exists(prefs_path):
with open(prefs_path, "r", encoding="utf-8") as f:
orig_prefs = f.read()
else:
orig_prefs = None
try:
# Remove prefs file to force fallback
if os.path.exists(prefs_path):
os.remove(prefs_path)
# Prepare settings.json general.logger_panel
cfg_gen = cfg.get_general_settings() or {}
cfg_gen["logger_panel"] = {"saved_levels": {"test.logger.fallback": "ERROR"}}
cfg.save_general_settings(cfg_gen)
# Ensure logger initial level is lower
logging.getLogger("test.logger.fallback").setLevel(logging.DEBUG)
apply_saved_logger_levels()
assert logging.getLogger("test.logger.fallback").level == logging.ERROR
finally:
# Restore originals
if orig_settings is not None:
with open(settings_path, "w", encoding="utf-8") as f:
f.write(orig_settings)
if orig_prefs is not None:
with open(prefs_path, "w", encoding="utf-8") as f:
f.write(orig_prefs)
else:
try:
if os.path.exists(prefs_path):
os.remove(prefs_path)
except Exception:
pass