SXXXXXXX_GitUtility/gitutility/logging_setup/log_handler.py
2025-05-05 10:28:19 +02:00

208 lines
8.2 KiB
Python

# --- 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_<numero>" 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 ---