Python è un lin­guag­gio di pro­gram­ma­zio­ne orientato agli oggetti molto diffuso. Molti svi­lup­pa­to­ri ap­prez­za­no Python perché questo lin­guag­gio di scripting permette di svi­lup­pa­re programmi più ve­lo­ce­men­te rispetto ai linguaggi di com­pi­la­zio­ne come Java. Rispetto ai linguaggi di pro­gram­ma­zio­ne pro­ce­du­ra­li "tra­di­zio­na­li" come Perl, Python ha il vantaggio di essere facile da leggere e da mantenere. Che si tratti di in­tel­li­gen­za ar­ti­fi­cia­le, in­ter­fac­cia utente grafica o am­mi­ni­stra­zio­ne di sistema: Python può essere uti­liz­za­to per una varietà di compiti. Ma più si utilizza un lin­guag­gio di pro­gram­ma­zio­ne, più diventa im­por­tan­te adottare una buona cultura del­l'er­ro­re. Il logging dovrebbe avvenire sin dalla prima fase di sviluppo fino all'uso effettivo da parte del­l'u­ten­te.

Nella libreria Python trovate un pratico modulo di logging. Sia per il semplice debug che per il logging centrale da server diversi, questo modulo di logging Python può rendere il lavoro degli svi­lup­pa­to­ri e degli operatori molto più semplice.

Che cos'è il logging?

Il termine "logging" deriva dalla parola inglese "log" e si riferisce, in questo contesto, a un pro­to­col­lo. Simile a un diario di bordo, contiene tutti i record im­por­tan­ti di un evento. A seconda di ciò che deve essere esaminato, vengono re­gi­stra­te solo alcune oppure tutte le azioni (o eventi) di un processo.

Quando si impara un nuovo lin­guag­gio di pro­gram­ma­zio­ne è normale com­met­te­re degli errori. Anche se Python è com­pren­si­bi­le per chi conosce già linguaggi di pro­gram­ma­zio­ne come C++ o Java, grazie alla si­mi­li­tu­di­ne delle strutture (ad esempio la forma dei loop), ogni lin­guag­gio ha le sue pe­cu­lia­ri­tà. Python, ad esempio, rap­pre­sen­ta le gerarchie mediante rientri. Anche il fun­zio­na­men­to del­l'ap­pli­ca­zio­ne più semplice sarà com­pro­mes­sa se per sbaglio viene omesso uno spazio. Un log degli errori indica agli svi­lup­pa­to­ri inesperti durante il debug la riga cor­ri­spon­den­te e l'errore "unex­pec­ted In­den­ta­tion". In questo caso, Python logging registra semplici errori di script e mostra un messaggio. Ma questo non è tutto. Gli svi­lup­pa­to­ri uti­liz­za­no il logging nei programmi per i compiti più disparati:

  • Debug: l'intero codice sorgente viene con­trol­la­to per ve­ri­fi­ca­re la presenza di errori e as­si­cu­ra­re che il programma finito funzioni senza problemi.
  • In­di­vi­dua­zio­ne e cor­re­zio­ne delle lacune di sicurezza: i possibili rischi vengono iden­ti­fi­ca­ti ed eliminati pre­ven­ti­va­men­te.
  • In­for­ma­ti­ca forense: può essere uti­liz­za­ta per de­ter­mi­na­re la causa di eventi critici, come gli attacchi hacker, mediante il file di log.
  • IT Auditing: mediante questo audit si determina se la sicurezza e l'in­te­gri­tà dei dati sono garantite, si con­fron­ta­no gli obiettivi aziendali con le strutture in­for­ma­ti­che esistenti per la loro com­pa­ti­bi­li­tà e si analizza l'ef­fi­cien­za dei programmi e dei sistemi operativi.
  • Confronto tra diverse versioni di record di dati: per ogni sessione viene creato un file di log separato in modo da poterli con­fron­ta­re in un secondo momento.

Il logging può generare molti dati, spe­cial­men­te quando si scrive un'ap­pli­ca­zio­ne complessa con Python. Gli svi­lup­pa­to­ri uti­liz­za­no Python Logging to File (un file di log creato dal modulo Python logging che viene popolato con le in­for­ma­zio­ni di logging tramite un handler) per rac­co­glie­re questi dati. È im­por­tan­te che il file di log funzioni in modo asincrono, al­tri­men­ti il logging in Python potrebbe bloccare l'e­se­cu­zio­ne dello script.

Analisi degli errori con Python logging: i 5 livelli di priorità dei log

Alcuni svi­lup­pa­to­ri uti­liz­za­no la funzione print per ve­ri­fi­ca­re la presenza di errori nei loro script. A tale scopo in­se­ri­sco­no l’istru­zio­ne in tutti i punti che po­treb­be­ro rap­pre­sen­ta­re una fonte di errore. Altri usano print persino in modo pre­ven­ti­vo al­l'in­ter­no dei loro script. Un problema di questo metodo è che poi dovranno ripassare l'intero codice per com­men­ta­re o can­cel­la­re l’istru­zio­ne. In caso contrario, il testo in uscita può apparire quando gli utenti uti­liz­za­no il programma. Il codice sorgente appare, inoltre, molto di­sor­di­na­to.

Il semplice logging consente di ri­spar­mia­re lavoro ag­giun­ti­vo e fornisce una soluzione più elegante per l'analisi degli errori. Il Python logging distingue cinque diversi livelli di gravità degli errori o “Level of Severity”. Potete ov­via­men­te creare anche filtri di log per­so­na­liz­za­ti. Il modulo logging di Python integrato di Vinay Sajip offre già una sud­di­vi­sio­ne utile dei livelli di gravità:

Nome del livello di logging Utilizzo Possibile messaggio
Debug Diagnosi dei problemi, molto det­ta­glia­ta Rientro ina­spet­ta­to nella linea XY
Info Fornisce un feedback sul corretto fun­zio­na­men­to del sistema La funzione 1*1 viene eseguita
Warning L'ap­pli­ca­zio­ne funziona cor­ret­ta­men­te, ma si è ve­ri­fi­ca­ta una si­tua­zio­ne im­pre­vi­sta o è stato lanciato un av­ver­ti­men­to per un problema futuro Lo spazio di ar­chi­via­zio­ne è quasi esaurito
Error Non è stato possibile eseguire una funzione perché si è ve­ri­fi­ca­to un problema Si è ve­ri­fi­ca­to un errore e l'azione è stata annullata
Critical Si è ve­ri­fi­ca­to un problema grave. Potrebbe essere ne­ces­sa­rio abortire l'intera ap­pli­ca­zio­ne Errore grave: il programma non può accedere a questo servizio e deve essere abortito

Ciascuno dei diversi livelli rap­pre­sen­ta in­for­ma­zio­ni su eventi di crescente im­por­tan­za. I livelli di Python logging sono funzioni statiche. Nella pro­gram­ma­zio­ne orientata agli oggetti queste funzioni co­sti­tui­sco­no il contenuto di una classe. Per ogni istanza della classe al­l'in­ter­no di un oggetto le funzioni statiche sono sempre le stesse. Non cambiano e sono presenti anche se non viene chiamata nessuna istanza. Error, ad esempio, è un messaggio di errore in tutte le istanze. Se viene chiamata nello stesso oggetto di ese­cu­zio­ne, il messaggio di errore associato rimane lo stesso. Per altre azioni è possibile spe­ci­fi­ca­re un messaggio di errore diverso.

Debug è il livello più basso, motivo per cui anche la priorità delle in­for­ma­zio­ni emesse è ridotta. Questo non significa, tuttavia, che la gravità di un errore sia superiore a quella di Critical. Debug include tutti gli altri livelli e genera, quindi, tutti i messaggi fino al Critical Error.

Il modulo logging di Python

Il modulo logging di Python fa parte della libreria Python. L'in­ter­fac­cia di logging non solo in­te­ra­gi­sce per­fet­ta­men­te con il resto del codice sorgente, ma è anche sempre pronta all'uso. Il semplice logging e l'invio delle in­for­ma­zio­ni a un file viene ra­pi­da­men­te integrata nel codice esistente tramite un handler. Il modulo logging di Python ha funzioni ag­giun­ti­ve che possono essere uti­liz­za­te per per­so­na­liz­za­re lo strumento. Questi sono i com­po­nen­ti prin­ci­pa­li del modulo logging:

  • Logger
  • Handler
  • Filter
  • Formatter

Le istanze vengono raccolte nell'istanza LogRecord e scambiano in­for­ma­zio­ni al­l'in­ter­no del­l'i­stan­za.

Logger

Gli ogetti logger re­gi­stra­no le azioni durante l'e­se­cu­zio­ne di un programma. I logger non vengono istan­zia­ti di­ret­ta­men­te, ma at­tra­ver­so la funzione logging.getLogger(name). Assegnate un nome al logger per vi­sua­liz­za­re, ad esempio, le gerarchie in modo strut­tu­ra­to. In Python separate i figli dai pacchetti con un punto. Il pacchetto log può quindi avere i pacchetti su­bor­di­na­ti log.bam o log.bar.loco. Ana­lo­ga­men­te, i logger lavorano in modo che l'oggetto "log" riceva le in­for­ma­zio­ni dei suoi figli "log.bam" e "log.bar.loco".

Handler

Gli oggetti handler ricevono le in­for­ma­zio­ni dai logger e le inoltrano. Gli handler sono una classe di base che determina come agisce l'in­ter­fac­cia delle istanze handler. Con la ri­spet­ti­va classe handler si definisce la de­sti­na­zio­ne. Strea­m­Hand­ler invia le in­for­ma­zio­ni agli stream, mentre Fi­le­Hand­ler le invia ai file. Potete uti­liz­za­re diversi handler per un programma che inviano messaggi dallo stesso logger. Questo è utile se, per esempio, volete vi­sua­liz­za­re le in­for­ma­zio­ni di debug nella console e im­por­tan­ti messaggi di errore in un file separato.

Con il metodo setLevel() potete impostare il livello minimo di gravità di un messaggio di log per essere inoltrato a questo handler. Invece di logger.setLevel (che determina il livello di logging), il metodo è chiamato [hand­ler­na­me].setLevel (vedi la riga 5 del modello di codice: fh.setLevel).

Formatter

A dif­fe­ren­za degli handler, i formatter (oggetti di for­mat­ta­zio­ne) possono essere istan­zia­ti di­ret­ta­men­te nel codice del­l'ap­pli­ca­zio­ne. Queste istanze vengono uti­liz­za­te per spe­ci­fi­ca­re il formato in cui il messaggio viene vi­sua­liz­za­to nel file di log. Se non uti­liz­za­te alcuna for­mat­ta­zio­ne, appare solo il messaggio del logger. Con la seguente funzione potete chiamare il formatter e definire il formato del messaggio e della data:

logging.Formatter.__init__(fmt=[formato messaggio], datefmt=[formato data])
#o anche:
logging.Formatter.__init__(fmt=None, datefmt=None)

Se non spe­ci­fi­ca­te un formato per la data nel­l'at­tri­bu­to, il formatter utilizza il formato americano: "Anno-Mese-Giorno Ore:Minuti:Secondi".

Filter

I filter con­sen­to­no di definire i messaggi in uscita in modo ancora più preciso. I filter vengono impostati per primi e poi aggiunti al cor­ri­spon­den­te handler o logger usando il metodo addFilter(). Se per via delle proprietà del messaggio il valore di un filter è false, questo non viene trasmesso. Uti­liz­za­te la funzione logging.Filter(name=fh), in cui l'at­tri­bu­to fh sta per qualsiasi nome di logger, per con­sen­ti­re i dati di log di un par­ti­co­la­re logger e bloccare tutti gli altri.

Un esempio pratico del modulo di logging di Python

Python mette a di­spo­si­zio­ne degli svi­lup­pa­to­ri il modulo Turtle per testare i comandi di base mediante un esempio. Nel­l'e­sem­pio che segue, l'utente utilizza Turtle. Questo strumento dovrebbe muoversi in avanti su uno sfondo verde, girare a sinistra, con­ti­nua­re a muoversi e poi disegnare un cerchio. Nel­l'e­sem­pio in­clu­dia­mo i comandi Python logging Info ed Error:

# -*- coding: UTF-8 -*-
import turtle
import logging
turtle.bgcolor("green")
turtle.fd(30)
turtle.lt(90)
turtle.fd(50)
logging.info('A te funziona.')
turtle.circle(50)
logging.error('Ups, qualcosa è andato storto.')

Nel­l'im­ma­gi­ne sopra potete vedere come appare il risultato. Il modulo Turtle (finestra a sinistra) ha accettato le istru­zio­ni e funziona come pre­scrit­to. Nella finestra a destra il codice include le istru­zio­ni Turtle e le istru­zio­ni di logging dei livelli INFO ed ERROR. La forma di output tipica di un messaggio di log è la seguente: [Gravità]:[Origine del messaggio]:[Contenuto del messaggio].

La console (Console 1/A) nel­l'e­sem­pio mostra, tuttavia, solo il messaggio di Python logging ERROR: Errore:root:Ups, qualcosa è andato storto.

Questo perché il modulo Python logging è impostato di default su WARNING. Il modulo omette tutte le in­for­ma­zio­ni più det­ta­glia­te fino a quando queste im­po­sta­zio­ni non vengono mo­di­fi­ca­te.

Mo­di­fi­ca­re il livello di Python logging

Uti­liz­za­te il seguente comando per cambiare l'im­po­sta­zio­ne al livello DEBUG:

import logging
logging.basicConfig(level=logging.DEBUG)

Nel­l'im­ma­gi­ne sopra la console vi­sua­liz­za il logging per ogni nuova chiamata. Se il programma viene abortito, la console cancella tutti i record. Per tenere traccia dei dati di logging è ne­ces­sa­rio uti­liz­za­re un file di log. Questa pratica viene chiamata Logging to File in inglese, ovvero logging su file.

Me­mo­riz­za­re il Python logging in un file

Il logging su file funziona in due modi. Potete creare un file di log mediante le im­po­sta­zio­ni di base o uti­liz­za­re un handler. Se non indicate una de­sti­na­zio­ne, Python logging memorizza le in­for­ma­zio­ni tem­po­ra­nea­men­te nella console.

Uti­liz­za­te il seguente comando per creare un file per il vostro Python logging:

import logging
logging.basicConfig( level=logging.DEBUG, filename='example.log')

Il Fi­le­Hand­ler è un'i­stan­za della classe di logging, che agisce insieme al­l'i­stan­za di logging. Essa definisce il luogo in cui vengono inviati i dati di logging e in quale forma. Il modulo di logging della libreria Python contiene altri logging handler oltre al Fi­le­Hand­ler, come lo Strea­m­Hand­ler e il Nul­lHand­ler. Si consiglia comunque di uti­liz­za­re un file di log per la suc­ces­si­va va­lu­ta­zio­ne dei dati di logging.

Ecco come potete creare un Fi­le­Hand­ler che memorizzi i messaggi di debug in un file:

Nel­l'im­ma­gi­ne sopra la funzione logging.getLogger() chiama il modulo logging di Python. "fh" è definito come Fi­le­Hand­ler con l'at­tri­bu­to "debug.log". Così fh crea il file di log "debug.log" e vi invia gli eventuali messaggi di log. Il metodo ad­d­Hand­ler() assegna l’handler cor­ri­spon­den­te al logger. Potete nominare li­be­ra­men­te il file di log.

Potete uti­liz­za­re le seguenti funzioni per fare una prova:

import logging
logger = logging.getLogger('Esempio_Log')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('debug.log')
fh.setLevel(logging.DEBUG)
logger.addHandler(fh)
logger.debug('Messaggio di debug')
logger.info('Messaggio info')
logger.warning('Avviso')
logger.error('Messaggio di errore')
logger.critical('Errore grave')

Se il file di log che avete creato con Python logging su file deve fornire in­for­ma­zio­ni utili per task specifici, i messaggi semplici po­treb­be­ro non essere suf­fi­cien­ti. Un timestamp e il nome del logger aiutano a clas­si­fi­ca­re meglio le note. La seguente schermata mostra un esempio di come potete spe­ci­fi­ca­re il formato uti­liz­zan­do gli attributi di Formatter. Nella finestra di Notepad debug.log, l'output di testo specifica i messaggi di log con data, ora, nome del logger, livello del log e messaggio.

Ecco ancora una volta il codice per fare una prova:

import logging
logger = logging.getLogger('Esempio_Log')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('debug.log')
fh.setLevel(logging.DEBUG)
logger.addHandler(fh)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.debug('Messaggio di debug')
logger.info('Messaggio info')
logger.warning('Avviso')
logger.error('Messaggio di errore')
logger.critical('Errore grave')

In sintesi

Python logging è uno strumento pratico per la pre­ven­zio­ne degli errori, il controllo dopo gli attacchi hacker o sem­pli­ce­men­te per l'analisi. Mentre altri linguaggi di pro­gram­ma­zio­ne uti­liz­za­no logging di terzi, il modulo di logging di Python fa parte della libreria standard. Se inserite il metodo nel vostro codice, vengono generati messaggi di log di diversi livelli, sia nei file che nella console. La for­mat­ta­zio­ne, i filtri e gli handler ga­ran­ti­sco­no un'im­po­sta­zio­ne facile. As­si­cu­ra­te­vi di scegliere nomi fa­cil­men­te ri­co­no­sci­bi­li per i vostri logger e i loro figli per rendere più facile il lavoro con i file di log in Python.

Vai al menu prin­ci­pa­le