Sostieni AppuntiFacili con una piccola donazione su PayPal

Dona con PayPal
AppuntiFacili
Torna Indietro Segnala errore

Logging

✍️ Dennis Turco 🏷️ Informatica 📘 Python
Ultima modifica:
#python#programmazione#logging

1. Introduzione

Il logging è un modo per tracciare eventi e messaggi che si verificano durante l’esecuzione del programma. A differenza del semplice print(), il logging offre un modo più professionale, scalabile e controllato per monitorare il comportamento del codice.

In particolare, con logging è possibile:

  • Registrare messaggi di diverso livello di importanza, come DEBUG, INFO, WARNING, ERROR e CRITICAL;
  • Controllare dove vengono scritti i messaggi di log, ad esempio su console, file, socket, o anche su server remoti;
  • Formattare i messaggi di log per includere informazioni come data, ora, nome del modulo, livello di gravità e molto altro;
  • Gestire i log in modo centralizzato, combinando più logger e handler per tenere traccia del comportamento dell’intera applicazione;
  • Filtrare i messaggi per registrare solo quelli che ti interessano, migliorando così le prestazioni e la leggibilità;
  • Diagnosticare e debuggare il codice in maniera più professionale e scalabile rispetto all’uso di semplici print().

INFO

Esempio pratico: Se un’applicazione desktop con interfaccia grafica dovesse andare in crash, grazie al logging potremmo salvare automaticamente su un file .log tutti gli eventi che hanno portato all’errore, facilitando la diagnosi del problema.

esempio

2. Tipi di logging

La libreria logging di Python prevede 5 livelli principali di gravità dei messaggi:

Livello di LogFunzioneDescrizione
DEBUGlogging.debug()Fornisce informazioni dettagliate utili per il debug e lo sviluppo.
INFOlogging.info()Fornisce informazioni generali su ciò che sta accadendo nel programma.
WARNINGlogging.warning()Indica che c’è qualcosa che merita attenzione, ma il programma può continuare a funzionare.
ERRORlogging.error()Segnala un problema imprevisto che si è verificato durante l’esecuzione del programma.
CRITICALlogging.critical()Indica un errore grave che potrebbe compromettere o interrompere il funzionamento dell’applicazione.

3. Utilizzo base

Ecco un esempio di utilizzo base del modulo logging:

import logging

# Configurazione base
logging.basicConfig(level=logging.DEBUG)

logging.debug("Messaggio di debug - utile per capire il flusso del programma")
logging.info("Messaggio informativo - il programma sta funzionando correttamente")
logging.warning("Attenzione - qualcosa potrebbe non andare come previsto")
logging.error("Errore - qualcosa è andato storto")
logging.critical("Errore critico - il programma potrebbe arrestarsi")

INFO

Di default, il livello di logging impostato è WARNING, quindi verranno mostrati solo i messaggi di livello WARNING, ERROR e CRITICAL. Per vedere anche i messaggi DEBUG e INFO, occorre impostare level=logging.DEBUG.

4. Salvare i log su file

È possibile scrivere i messaggi di log anche su un file, utile per tracciare gli eventi nel tempo:

import logging

logging.basicConfig(
    filename="app.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.info("Programma avviato correttamente.")
logging.warning("Connessione lenta.")
logging.error("Impossibile aprire il file richiesto.")

Questo codice genererà un file chiamato app.log contenente i messaggi con data, ora, livello e testo.

5. Formattazione dei messaggi di log

Il parametro format permette di personalizzare il formato del messaggio.

VariabileDescrizione
%(asctime)sData e ora del log
%(levelname)sLivello del messaggio
%(message)sTesto del messaggio
%(filename)sNome del file Python che ha generato il log
%(lineno)dNumero di riga del file
%(name)sNome del logger (utile con più logger personalizzati)

Esempio:

logging.basicConfig(
    format="%(asctime)s - [%(levelname)s] (%(filename)s:%(lineno)d) - %(message)s",
    level=logging.DEBUG
)

6. Creazione di logger personalizzati

In applicazioni più complesse, è buona pratica creare logger personalizzati, invece di usare la configurazione globale.

import logging

# Creazione di un logger
logger = logging.getLogger("app_logger")
logger.setLevel(logging.DEBUG)

# Creazione di un handler per la console
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# Creazione di un handler per il file
file_handler = logging.FileHandler("dettagli.log")
file_handler.setLevel(logging.DEBUG)

# Formattazione
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# Aggiunta degli handler al logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# Esempio di log
logger.debug("Questo messaggio va solo nel file.")
logger.info("Questo messaggio appare su console e file.")
logger.error("Errore critico!")

INFO

Questa tecnica è molto utile per progetti complessi, dove diversi moduli del programma generano log separati ma centralizzati.

7. Logging unico in OOP

In un progetto medio-grande, specialmente con approccio Object-Oriented, il logging è fondamentale per:

  • Tracciare l’esecuzione del codice
  • Registrare errori e warning
  • Analizzare lo stato dell’applicazione
  • Fare debug senza usare print()
  • Produrre log sia su console che su file

Il problema è che senza un servizio centralizzato, ogni file rischia di creare logger non uniformi o duplicare handlers, generando log doppi e configurazioni incoerenti.

Un LogService risolve tutto questo: una singola configurazione una sola volta, e poi in ogni modulo chiedi semplicemente:

logger = LogService.get_logger(__name__)

Così hai:

  • formattazione uniforme
  • log su file e console
  • nessuna duplicazione di handler
  • configurazione eseguita una sola volta

File: LogService.py:

import logging
from pathlib import Path
from typing import Optional

# Configurazioni globali
log_level = "INFO"
log_filename = "app.log"
full_path = Path(__file__).parent / log_filename


class LogService:
    _is_configured = False

    @classmethod
    def get_logger(cls, name: Optional[str] = None):
        if not cls._is_configured:
            cls._configure()
        return logging.getLogger(name)

    @classmethod
    def _configure(cls):
        root_logger = logging.getLogger()
        root_logger.setLevel(logging.getLevelName(log_level.upper()))

        formatter = logging.Formatter(
            "%(asctime)s - [%(levelname)s] (%(filename)s:%(lineno)d) - %(message)s"
        )

        # Console handler
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)

        # File handler
        file_handler = logging.FileHandler(full_path, encoding="utf-8")
        file_handler.setFormatter(formatter)

        # Evitiamo di aggiungere handler doppi
        if not root_logger.handlers:
            root_logger.addHandler(console_handler)
            root_logger.addHandler(file_handler)

        cls._is_configured = True

Esempio d’uso, file mail.py:

from LogService import LogService

class MailService:

    def __init__(self):
        self.logger = LogService.get_logger(__name__)

    def send_email(self, to: str, subject: str, body: str):
        self.logger.info(f"Invio email a: {to}")
        try:
            # simulazione invio email
            if not to:
                raise ValueError("Indirizzo email mancante")

            # Log aggiuntivi
            self.logger.debug(f"Oggetto: {subject}")
            self.logger.debug(f"Corpo: {body}")

            # Simulazione invio riuscito
            self.logger.info("Email inviata con successo.")

        except Exception as e:
            self.logger.error(f"Errore nell'invio della mail: {e}")
            raise

8. Esercizi

8.1 Esercizio - Logging Base

Scrivi un programma che:

  1. utilizzi logging.basicConfig() per impostare il livello a DEBUG;
  2. generi messaggi per ogni livello (DEBUG, INFO, WARNING, ERROR, CRITICAL);
  3. osserva quali livelli vengono mostrati modificando il parametro level.

8.2 Esercizio - Log su File

Crea uno script che:

  1. salvi i messaggi di log in un file chiamato operazioni.log;
  2. includa nel formato data, ora, livello e messaggio;
  3. registri eventi simulando il funzionamento di un’app (es. “Connessione stabilita”, “Errore di rete”, ecc.).

Punti extra:

  1. Crea una classe Applicazione che:
    1. contenga un metodo esegui_operazione() che simula varie azioni (es. connessione, caricamento file, elaborazione dati);
    2. utilizzi il modulo logging per registrare ogni evento con il livello appropriato (INFO, WARNING, ERROR).
  2. Gestisci possibili eccezioni con un blocco try-except, ad esempio:
    1. se viene simulato un errore (es. file mancante o connessione persa), catturalo con except Exception as e;
    2. registra l’errore nel file di log con logging.error(str(e)).

INFO

Usa random.choice() per simulare eventi casuali positivi o di errore e osservali nel file operazioni.log.

8.3 Esercizio - Logger Personalizzato

Crea un logger personalizzato chiamato server_logger che:

  1. invii i log INFO e superiori alla console;
  2. invii tutti i log (anche DEBUG) a un file server_debug.log;
  3. utilizzi un formato che includa asctime, levelname, filename e message.

Punti extra:

  1. Crea una classe Server che:
    1. definisca un attributo logger inizializzato nel costruttore;
    2. contenga metodi come avvia(), elabora_richiesta() e arresta() che scrivono log con diversi livelli (INFO, DEBUG, ERROR).
  2. All’interno dei metodi della classe, utilizza try-except per gestire errori simulati (es. ValueError, ConnectionError) e registra:
    1. un messaggio ERROR con la descrizione dell’eccezione;
    2. un messaggio CRITICAL se l’errore è grave e il server deve essere arrestato.

INFO

Puoi creare un’eccezione personalizzata, ad esempio ServerCrashError(Exception), e sollevarla (raise) quando vuoi simulare un errore critico. Nel blocco except, logga l’evento con logger.critical("Arresto del server: errore critico!").

9. Quiz a risposta multipla

1) Qual è la funzione principale del modulo logging in Python?

2) Qual è il livello di logging predefinito in Python?

3) Cosa fa il parametro 'filename' in logging.basicConfig()?

4) Quale livello di log si usa per messaggi di debug?

5) Cosa succede se non imposti 'level=logging.DEBUG' nella configurazione?

6) A cosa serve un 'logger personalizzato'?

7) Cosa rappresenta '%(asctime)s' nel formato dei log?

8) Come si definisce un formato personalizzato per i log?

9) Quale dei seguenti livelli è più grave?

10) Qual è una buona pratica nell'uso del logging?

Prenota una lezione