Corso Python / Leggere traceback e usare debugger e logging

Per individuare un errore, leggi prima il tipo di eccezione e il messaggio finale del traceback, poi riproduci l’ingresso che l’ha causato. Il debugger permette di osservare lo stato durante l’esecuzione; logging registra gli eventi scelti dal programma con un livello di gravità.

Ridurre il problema a un caso riproducibile

Parti dall’ultima riga del traceback e risali ai file e alle righe del tuo codice nella catena di chiamate. La riga segnalata mostra dove il guasto è emerso, non sempre dove è stato introdotto: un valore errato può essere stato preparato prima e passato a una libreria perfettamente funzionante.

Annota il comando, l’interprete del progetto, i dati di prova e il risultato atteso. Riduci il caso rimuovendo le parti non necessarie, senza eliminare proprio le condizioni che lo producono. Correggere il primo errore reale è più utile che modificare indiscriminatamente il codice delle dipendenze.

In VS Code, con il progetto e l’interprete corretti selezionati e il supporto Python Debugger disponibile, apri il file e imposta un breakpoint nel margine accanto alla riga da osservare. Avvia Run and Debug, scegli l’esecuzione del file Python corrente quando richiesto e controlla Variables e Call Stack mentre l’esecuzione è sospesa. Usa Step Over per seguire le istruzioni e osserva i dati prima del punto di errore. La guida ufficiale al debugger Python in VS Code descrive configurazioni e controlli.

Nell’esempio seguente puoi fermarti sulla condizione che controlla i valori negativi e ispezionare amounts. La distinzione tra intercettare e propagare un errore è approfondita nella guida alle eccezioni.

Eseguire un calcolo con logging

Salva il codice completo come logging_demo.py, in UTF-8, in una cartella di esercizio. Avvialo dal terminale con python logging_demo.py, usando l’interprete del progetto. Fuori dall’ambiente virtuale su macOS o Ubuntu usa python3 se è il nome dell’interprete scelto. Non occorrono pacchetti esterni.

import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
logger = logging.getLogger(__name__)

def calculate(amounts):
    logger.debug("count=%s", len(amounts))
    if any(x < 0 for x in amounts):
        raise ValueError("negative amount")
    return sum(amounts)

try:
    total = calculate([3500, 8000, 2500])
except ValueError:
    logger.exception("calculation failed")
    raise SystemExit(1)
else:
    logger.info("total=%s", total)
print("finished")

Sul canale di uscita standard, stdout, il risultato previsto è soltanto:

finished

Sul canale di errore standard, stderr, viene inoltre emesso INFO:total=14000. Un terminale può visualizzarli insieme, ma restano due flussi differenti. Il messaggio DEBUG sul numero di elementi non compare con la configurazione INFO.

BasicConfig imposta la configurazione di base del logging; qui il programma viene avviato come nuovo processo. In ambienti interattivi già configurati, basicConfig può non modificare i gestori esistenti, quindi non usare l’assenza di un cambiamento come prova che il codice del livello non funzioni. Il logger prende il nome del modulo mediante __name__.

Distinguere livello, canale e stato di uscita

Livello Uso tipico
DEBUG Dettagli per la diagnosi
INFO Avanzamento o risultati normali
WARNING Condizione che richiede attenzione
ERROR Operazione fallita
CRITICAL Problema grave per la continuità del programma

La scelta del livello segue una politica coerente nell’applicazione. Nel nostro caso INFO rende visibile il totale e nasconde count=3. La guida ufficiale a logging spiega livelli e configurazione.

logger.exception, usato nel gestore di un’eccezione, aggiunge al messaggio ERROR anche il traceback. Non provoca da solo un’uscita fallita: il codice deve decidere se recuperare o interrompere. Qui raise SystemExit(1) termina il processo con stato 1 dopo il log di un ValueError non recuperabile; il successivo print non viene eseguito.

Con i dati originali il processo termina con stato 0; con un importo negativo termina con stato 1 e non stampa finished. Subito dopo il comando controlla $LASTEXITCODE in PowerShell oppure echo $? nella shell di macOS o Ubuntu. La documentazione di sys.exit e SystemExit chiarisce il significato dei valori di uscita.

Non dedurre l’ordine temporale completo dall’ordine con cui un sistema riunisce stdout e stderr: acquisizione e buffering possono influire sulla visualizzazione. Se scegli di registrare su file, definisci anche destinazione, rotazione, conservazione e permessi; una chiamata di log non stabilisce da sola queste regole operative.

Provare un errore e ripristinare il comportamento

  1. Esegui l’originale in un nuovo processo: verifica finished su stdout, INFO:total=14000 su stderr e stato 0.
  2. In una copia cambia soltanto 8000 in -1 nell’elenco passato a calculate. Verifica ERROR:calculation failed, il traceback con ValueError: negative amount e stato 1. Finished non deve comparire.
  3. Conservando i dati negativi, cambia il livello iniziale da logging.INFO a logging.DEBUG. Deve comparire anche DEBUG:count=3 prima della segnalazione dell’errore.
  4. Ripristina dati e livello originali, poi verifica di nuovo il risultato normale.

L’esempio con una lista vuota restituisce un totale zero e non rappresenta una validazione completa dei dati di dominio. Se servono regole ulteriori, rendile esplicite. Evita di sostituire il gestore con un except generico vuoto: rischieresti di stampare finished anche dopo un calcolo fallito.

Registra il contesto utile, senza password, token, intestazioni di autenticazione o interi record personali. Limitare dati e frequenza dei messaggi riduce sia l’esposizione di informazioni sia il consumo di tempo e spazio. L’utente deve ricevere un’indicazione per agire; chi diagnostica il problema ha bisogno del contesto selezionato per ricostruirlo.