# --- FILE: gitsync_tool/logging_setup/log_handler.py --- import queue import logging # Importa il modulo logging per usare le costanti di livello (es. logging.INFO) import datetime import traceback # Per la funzione log_exception import sys # Per log_exception su stderr from typing import Dict, Any, Optional # Aggiunti type hints # --- Coda Condivisa Thread-Safe --- # Usiamo una coda standard di Python, che è intrinsecamente thread-safe. # La dimensione è illimitata per default. Se si verificassero problemi di memoria # in casi estremi, si potrebbe considerare queue.Queue(maxsize=...). log_queue: queue.Queue[Dict[str, Any]] = queue.Queue() # --- Mappa Livelli di Logging (per nomi leggibili) --- # Ricalca quella che potrebbe essere definita anche in logger_config # per consistenza, ma è utile averla qui per _format_log_message. LOG_LEVEL_MAP: Dict[int, str] = { logging.CRITICAL: "CRITICAL", logging.ERROR: "ERROR", logging.WARNING: "WARNING", logging.INFO: "INFO", logging.DEBUG: "DEBUG", logging.NOTSET: "NOTSET", } def get_log_level_name(level_number: int) -> str: """ Restituisce il nome stringa leggibile per un livello di logging numerico. Args: level_number (int): Il livello di logging numerico (es. logging.INFO). Returns: str: Il nome del livello (es. "INFO") o "LEVEL_" per livelli non standard. """ # Usa la mappa, con un fallback per livelli non definiti nella mappa return LOG_LEVEL_MAP.get(level_number, f"LEVEL_{level_number}") def _format_log_message(level: int, message: str, func_name: Optional[str]) -> str: """ Funzione interna helper per formattare il messaggio di log standard. Args: level (int): Il livello di logging numerico. message (str): Il messaggio di log grezzo. func_name (Optional[str]): Il nome della funzione chiamante (opzionale). Returns: str: Il messaggio di log formattato con timestamp, livello e nome funzione. """ try: # Ottieni timestamp corrente timestamp: str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[ :-3 ] # Aggiunge millisecondi # Ottieni nome livello level_name: str = get_log_level_name(level) # Includi il nome della funzione solo se fornito func_part: str = f" [{func_name}]" if func_name else "" # Formato: Timestamp,ms - LEVEL - [FuncName] - Messaggio formatted_message: str = ( f"{timestamp} - {level_name:<8} -{func_part} - {message}" # Allinea nome livello ) return formatted_message except Exception as e: # Fallback molto basico in caso di errori nella formattazione stessa print( f"!!! ERROR FORMATTING LOG MESSAGE: {e} !!! Original: {message}", file=sys.stderr, ) return f"{datetime.datetime.now()} - FORMAT_ERROR - {message}" # Ritorna messaggio minimale def log_message(level: int, message: str, func_name: Optional[str] = None) -> None: """ Formatta un messaggio di log e lo mette nella coda condivisa 'log_queue' come dizionario per essere processato dal gestore della coda. Args: level (int): Il livello di logging numerico (es. logging.INFO, logging.DEBUG). message (str): Il messaggio di log da registrare. func_name (Optional[str]): Nome opzionale della funzione chiamante per contesto. """ # 1. Formatta il messaggio per la visualizzazione/file formatted_message: str = _format_log_message(level, message, func_name) # 2. Crea il dizionario per la coda # Include sia il messaggio già formattato che il livello originale. # Il processore della coda userà 'message' per display/file e 'level' per filtraggio/colorazione. log_entry: Dict[str, Any] = { "level": level, "message": formatted_message, # Potremmo aggiungere altri campi qui se necessario (es. thread ID) } # 3. Metti nella coda try: # Usa put_nowait per evitare blocco se la coda fosse piena (improbabile) log_queue.put_nowait(log_entry) except queue.Full: # Fallback critico se la coda è piena: stampa su stderr print( f"!!! LOG QUEUE FULL - Dropping message: {formatted_message}", file=sys.stderr, ) # Considera di loggare questo evento anche su file se possibile except Exception as e: # Fallback per altri errori di accodamento print( f"!!! ERROR putting message in log queue: {e} - Message: {formatted_message}", file=sys.stderr, ) # --- Funzioni Helper per Livelli Specifici --- # Queste funzioni forniscono un'interfaccia più semplice per loggare # ai livelli comuni, passando automaticamente il livello corretto a log_message. def log_debug(message: str, func_name: Optional[str] = None) -> None: """Invia un messaggio di livello DEBUG alla coda log.""" log_message(logging.DEBUG, message, func_name) def log_info(message: str, func_name: Optional[str] = None) -> None: """Invia un messaggio di livello INFO alla coda log.""" log_message(logging.INFO, message, func_name) def log_warning(message: str, func_name: Optional[str] = None) -> None: """Invia un messaggio di livello WARNING alla coda log.""" log_message(logging.WARNING, message, func_name) def log_error(message: str, func_name: Optional[str] = None) -> None: """Invia un messaggio di livello ERROR alla coda log.""" log_message(logging.ERROR, message, func_name) def log_critical(message: str, func_name: Optional[str] = None) -> None: """Invia un messaggio di livello CRITICAL alla coda log.""" log_message(logging.CRITICAL, message, func_name) def log_exception(message: str, func_name: Optional[str] = None) -> None: """ Invia un messaggio di livello ERROR per un'eccezione e stampa il traceback completo sulla console/stderr per diagnostica immediata. Il messaggio nella coda includerà un riferimento al traceback su console. """ # 1. Logga un messaggio ERROR nella coda, indicando che il traceback è altrove error_msg_for_queue: str = f"{message} (See console/stderr for exception details)" log_message(logging.ERROR, error_msg_for_queue, func_name) # 2. Stampa il traceback completo su stderr # Questo è visibile solo se l'applicazione è eseguita da un terminale # o se stderr viene reindirizzato. Non va nella coda log principale. print(f"\n--- EXCEPTION TRACEBACK ({datetime.datetime.now()}) ---", file=sys.stderr) print( f"Context: Function='{func_name or 'N/A'}', Message='{message}'", file=sys.stderr, ) # Stampa l'eccezione corrente e il suo traceback traceback.print_exc(file=sys.stderr) print(f"--- END TRACEBACK ---\n", file=sys.stderr) # --- Esempio di utilizzo (eseguito solo se si lancia questo file direttamente) --- if __name__ == "__main__": print("Example usage of log_handler (messages are put into the queue):") log_info("This is an informational message.", func_name="example_usage") log_debug("This is a debug message.", func_name="example_usage") log_warning("This is a warning message.", func_name="example_usage") log_error("This is an error message.", func_name="example_usage") try: result = 1 / 0 except ZeroDivisionError: log_exception( "An exception occurred during calculation.", func_name="example_error_handling", ) print(f"\nCurrent log queue size: {log_queue.qsize()}") print("\nRetrieving messages from the queue:") processed_count = 0 while not log_queue.empty(): try: entry = log_queue.get_nowait() print( f" Level: {entry.get('level')}, Formatted Message: {entry.get('message')}" ) processed_count += 1 except queue.Empty: break # Should not happen with check, but good practice except Exception as e: print(f"Error getting from queue: {e}") print(f"\nProcessed {processed_count} messages from queue.") print("\nNote: Exception traceback (if any) was printed directly to stderr above.") # --- END OF FILE gitsync_tool/logging_setup/log_handler.py ---