208 lines
8.2 KiB
Python
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 ---
|