SXXXXXXX_FlightMonitor/flightmonitor/utils/logger.py
VALLONGOL 27e8459438 Chore: Stop tracking files based on .gitignore update.
Untracked files matching the following rules:
- Rule "!.vscode/launch.json": 1 file
2025-05-15 15:54:09 +02:00

155 lines
7.9 KiB
Python

# FlightMonitor/utils/logger.py
import logging
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
import threading # Per ottenere il main_thread
# Importazioni relative esplicite
from ..data import config as app_config
# ... (costanti come prima) ...
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
DEFAULT_LOG_LEVEL = logging.INFO
LOG_LEVEL_COLORS = {
logging.DEBUG: getattr(app_config, 'LOG_COLOR_DEBUG', 'gray'),
logging.INFO: getattr(app_config, 'LOG_COLOR_INFO', 'black'),
logging.WARNING: getattr(app_config, 'LOG_COLOR_WARNING', 'orange'),
logging.ERROR: getattr(app_config, 'LOG_COLOR_ERROR', 'red'),
logging.CRITICAL: getattr(app_config, 'LOG_COLOR_CRITICAL', 'red4')
}
class TkinterTextHandler(logging.Handler):
def __init__(self, text_widget: tk.Text):
super().__init__()
self.text_widget = text_widget
# Salva un riferimento al thread principale (GUI)
self.gui_thread = threading.current_thread() # O threading.main_thread() se si è sicuri che venga chiamato da lì
# threading.main_thread() è generalmente più sicuro per questo scopo.
# Verifica se text_widget è valido prima di configurare i tag
if self.text_widget and self.text_widget.winfo_exists():
for level, color in LOG_LEVEL_COLORS.items():
if color:
try:
self.text_widget.tag_config(logging.getLevelName(level), foreground=color)
except tk.TclError:
# Potrebbe accadere se il widget è in uno stato strano durante l'init
# anche se winfo_exists() dovrebbe coprirlo.
print(f"Warning: Could not config tag for {logging.getLevelName(level)} during TkinterTextHandler init.")
pass
else:
# Non fare nulla o logga un avviso se il widget non è valido all'inizio
# (anche se questo non dovrebbe accadere se setup_logging è chiamato correttamente)
print("Warning: TkinterTextHandler initialized with an invalid text_widget.")
def emit(self, record: logging.LogRecord):
msg = self.format(record)
level_name = record.levelname
# Controlla se siamo nel thread della GUI e se il widget esiste ancora ed è valido
# E, cosa più importante, se il mainloop è ancora "attivo" o se l'interprete è in shutdown
# Un modo semplice per verificarlo è vedere se il widget può essere acceduto senza errori Tcl.
# O, meglio, usare threading.main_thread().is_alive() è troppo generico.
# L'errore "main thread is not in main loop" è specifico di Tkinter.
# La cosa migliore è provare e catturare l'eccezione TclError.
if not (self.text_widget and self.text_widget.winfo_exists()):
# Se il widget non esiste più, non tentare di loggare su di esso.
# Potremmo voler loggare sulla console come fallback.
# print(f"Fallback to console (widget destroyed): {msg}")
return # Non fare nulla se il widget non esiste
try:
# Questo blocco è la parte critica. Se fallisce, il mainloop potrebbe non essere attivo.
self.text_widget.configure(state=tk.NORMAL)
self.text_widget.insert(tk.END, msg + "\n", (level_name,))
self.text_widget.see(tk.END)
self.text_widget.configure(state=tk.DISABLED)
self.text_widget.update_idletasks() # Questo potrebbe essere problematico se non nel mainloop
except tk.TclError as e:
# Questo errore ("main thread is not in main loop" o simili)
# indica che non possiamo più interagire con Tkinter.
# Logga sulla console come fallback.
# print(f"TkinterTextHandler TclError (GUI likely closing): {e}")
# print(f"Original log message (fallback to console): {msg}")
# Non stampare nulla qui per evitare confusione con il log della console già esistente
pass # Silenzia l'errore, il messaggio è già andato alla console
except RuntimeError as e:
# Ad esempio "Too early to create image" o altri errori di runtime di Tkinter
# durante la chiusura.
# print(f"TkinterTextHandler RuntimeError (GUI likely closing): {e}")
# print(f"Original log message (fallback to console): {msg}")
pass # Silenzia l'errore
except Exception as e:
# Altri errori imprevisti
print(f"Unexpected error in TkinterTextHandler.emit: {e}")
print(f"Original log message (fallback to console): {msg}")
# ... (setup_logging e get_logger come prima) ...
_tkinter_handler = None
def setup_logging(gui_log_widget: tk.Text = None):
global _tkinter_handler
log_level_str = getattr(app_config, 'LOG_LEVEL', 'INFO').upper()
log_level = getattr(logging, log_level_str, DEFAULT_LOG_LEVEL)
log_format = getattr(app_config, 'LOG_FORMAT', DEFAULT_LOG_FORMAT)
date_format = getattr(app_config, 'LOG_DATE_FORMAT', DEFAULT_LOG_DATE_FORMAT)
formatter = logging.Formatter(log_format, datefmt=date_format)
# Usa il logger radice del nostro pacchetto se vogliamo isolarlo.
# Per ora, continuiamo con il root logger globale.
# logger_name_to_configure = 'flightmonitor' # o '' per il root globale
# current_logger = logging.getLogger(logger_name_to_configure)
current_logger = logging.getLogger() # Root logger
current_logger.setLevel(log_level)
# Rimuovi solo gli handler che potremmo aver aggiunto noi
# per evitare di rimuovere handler di altre librerie (se si usa il root logger globale)
# È più sicuro se TkinterTextHandler è l'unico handler che potremmo aggiungere più volte
# o se gestiamo esplicitamente quali handler rimuovere.
if _tkinter_handler and _tkinter_handler in current_logger.handlers:
current_logger.removeHandler(_tkinter_handler)
_tkinter_handler = None # Resetta così ne creiamo uno nuovo
# Console Handler (aggiungilo solo se non ne esiste già uno simile)
has_console_handler = any(isinstance(h, logging.StreamHandler) and \
not isinstance(h, TkinterTextHandler) for h in current_logger.handlers)
if not has_console_handler:
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
console_handler.setLevel(log_level)
current_logger.addHandler(console_handler)
# current_logger.info("Console logging handler added.") # Logga solo se aggiunto
# else:
# current_logger.debug("Console handler already exists.")
if gui_log_widget:
_tkinter_handler = TkinterTextHandler(gui_log_widget)
_tkinter_handler.setFormatter(formatter)
_tkinter_handler.setLevel(log_level)
current_logger.addHandler(_tkinter_handler)
if current_logger.isEnabledFor(logging.INFO): # Logga solo se il livello lo permette
current_logger.info("GUI logging handler initialized/updated.")
# else:
# if current_logger.isEnabledFor(logging.INFO):
# current_logger.info("Console logging active (no GUI widget provided for logging).")
def get_logger(name: str) -> logging.Logger:
# Se current_logger in setup_logging fosse 'flightmonitor', allora qui:
# if not name.startswith(logger_name_to_configure + '.'):
# qualified_name = logger_name_to_configure + '.' + name.lstrip('.')
# else:
# qualified_name = name
# return logging.getLogger(qualified_name)
return logging.getLogger(name)
# Il blocco if __name__ == "__main__" per il test di logger.py è meglio rimuoverlo o
# commentarlo pesantemente, dato che dipende da una struttura di pacchetto per
# l'import di app_config e ora ha una logica più complessa.
# Testare attraverso l'esecuzione dell'applicazione principale è più affidabile.