Uno script che funziona quando lo lanciate voi, davanti allo schermo, è solo a metà strada. Quando gira da solo alle sei del mattino, nessuno vede i messaggi di errore, nessuno nota che un file mancava, nessuno si accorge che ha elaborato zero righe invece di trecento. La differenza tra uno script di prova e un'automazione su cui contare sta in pochi accorgimenti: un registro delle attività, una gestione esplicita degli errori e un modo per avvisare qualcuno quando qualcosa va storto.
Sostituire print con logging
Il modulo logging della libreria standard fa ciò che fa print, ma con tre vantaggi: scrive su file, aggiunge automaticamente data e ora, e distingue i livelli di gravità.
import logging
logging.basicConfig(
filename="C:/Automazioni/log/fatture.log",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
log = logging.getLogger("fatture")
log.info("Avvio elaborazione")
log.warning("File senza data nel nome: %s", "scansione.pdf")
log.error("Impossibile aprire %s", "ordine_15.pdf")
Ogni riga del file avrà la forma 2025-03-10 06:00:02,315 INFO Avvio elaborazione. Quando un collega segnala che un documento non è stato archiviato, basta cercare il nome del file nel log per sapere se è stato visto, quando e con quale esito.
I livelli principali, dal meno al più grave, sono DEBUG, INFO, WARNING, ERROR e CRITICAL. Impostando level=logging.INFO, i messaggi di debug vengono ignorati: si possono lasciare nel codice e riattivare quando serve indagare.
Evitare che il log cresca all'infinito
Uno script che gira ogni giorno per anni produce un file enorme. Il modulo logging.handlers offre RotatingFileHandler, che crea un nuovo file al superamento di una certa dimensione, e TimedRotatingFileHandler, che lo fa a intervalli di tempo, conservando solo un numero stabilito di copie precedenti.
Gestire le eccezioni senza nasconderle
Quando un errore non viene gestito, Python interrompe lo script e stampa il messaggio sul terminale, che in un'esecuzione pianificata nessuno legge. L'errore opposto è altrettanto pericoloso: un try con un except vuoto che ingoia ogni problema e lascia credere che tutto sia andato bene.
La strada giusta sta nel mezzo. Si gestiscono gli errori di un singolo elemento senza fermare il lavoro sugli altri, e li si registra:
errori = 0
for file in elenco_file:
try:
elabora(file)
log.info("Elaborato %s", file.name)
except Exception:
errori += 1
log.exception("Errore su %s", file.name)
log.exception registra il messaggio insieme allo stack trace completo, cioè l'elenco delle righe di codice che hanno portato all'errore. È l'informazione che serve per capire il problema a distanza di giorni.
Codici di uscita
Uno script comunica il proprio esito al sistema operativo con un codice di uscita: zero se tutto è andato bene, un valore diverso in caso di problemi. L'Utilità di pianificazione di Windows e cron lo registrano, e altri programmi possono basarsi su di esso.
import sys
log.info("Fine: %d file, %d errori", len(elenco_file), errori)
sys.exit(1 if errori else 0)
Senza questa riga lo script termina sempre con zero, anche quando metà dei file è fallita, e l'Utilità di pianificazione mostrerà un rassicurante 0x0.
Avvisare qualcuno
Il log è prezioso, ma va letto. Per gli errori serve una notifica attiva. Le opzioni più comuni:
| Metodo | Vantaggi | Limiti |
|---|---|---|
| Semplice, arriva ovunque | Rischia di finire ignorata tra le altre | |
| Messaggio in una chat di lavoro | Visibile al gruppo | Richiede la configurazione di un'integrazione |
| File di stato in una cartella condivisa | Nessun servizio esterno | Qualcuno deve controllarlo |
Una buona regola è notificare solo ciò che richiede un intervento. Un'email a ogni esecuzione riuscita viene presto ignorata, e con lei quella che segnala un problema. Nel mio lavoro invio una notifica solo in caso di errore e un breve riepilogo settimanale che conferma che tutto ha funzionato.
Controlli di buon senso
Alcuni problemi non sono errori tecnici e non generano eccezioni. Lo script gira senza problemi, ma il risultato è sbagliato. Qualche verifica esplicita li intercetta:
- zero elementi: se la cartella di input è vuota in un giorno in cui non dovrebbe esserlo, è un'anomalia da segnalare;
- valori fuori scala: un totale dieci volte superiore alla media merita un avviso;
- file di destinazione: controllare che il file prodotto esista e non sia vuoto;
- blocco contro esecuzioni sovrapposte: un file di blocco creato all'avvio e rimosso alla fine impedisce che due istanze dello stesso script lavorino insieme sugli stessi file.
if not elenco_file:
log.warning("Nessun file da elaborare")
Configurazione separata dal codice
Percorsi, indirizzi email dei destinatari e soglie di allarme cambiano più spesso della logica. Tenerli in un file di configurazione, per esempio in formato TOML letto con il modulo tomllib disponibile da Python 3.11, evita di modificare il codice per ogni piccolo cambiamento e riduce il rischio di introdurre errori.
In sintesi
- Usate
loggingcon data, ora e livelli al posto diprint, e fate ruotare i file di log. - Gestite gli errori per singolo elemento e registrateli con
log.exception. - Restituite un codice di uscita diverso da zero quando qualcosa fallisce.
- Notificate solo ciò che richiede un intervento, più un riepilogo periodico.
- Aggiungete controlli su risultati vuoti o anomali e impedite esecuzioni sovrapposte.