# 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.