logging - Riporta messaggi di stato, di errore ed informativi

Scopo Riporta messaggi di stato, di errore ed informativi
Versione Python 2.3

Il modulo logging definisce una API standard per segnalare errori ed informazioni di stato da applicazioni e librerie. Il vantaggio chiave dell'avere l'API di logging fornita da un modulo di libreria standard è che tutti i moduli Python possono concorrere al logging, in modo che il log della propria applicazione possa includere messaggi da moduli di terze parti.

Log nelle Applicazioni

Ci sono due prospettive da cui esaminare i log. Gli sviluppatori dell'applicazione imposta il modulo logging, smistando i messaggi agli appropriati canali in uscita. E' possibile registrare messaggi con livelli di dettaglio diversi oppure verso destinazioni diverse. Gestori per la scrittura dei messaggi di log a file, ad indirizzi HTTP GET/POST, email tramite SMTP, socket generici oppure verso meccanismi di registrazione specifici di un sistema operativo sono tutti inclusi ed è possibile creare le proprie classi di destinazione del log se si hanno specifiche esigenze che non sono coperte dalle classi built-in.

Logging ad un file

La maggior parte delle applicazioni probabilmente vorranno eseguire il log ad un file. Si usa la funzione basicConfig() per impostare il gestore predefinito in modo che i messaggi di debug siano scritti ad un file.

import logging

LOG_FILENAME = 'logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,
                    level=logging.DEBUG,
                    )

logging.debug('Questo messaggio dovrebbe andare nel file di log')

f = open(LOG_FILENAME, 'rt')
try:
    body = f.read()
finally:
    f.close()

print 'FILE:'
print body

Dopo l'esecuzione dello script il messaggio di log viene scritto in :

 logging_file_example.py

FILE:
DEBUG:root:Questo messaggio dovrebbe andare nel file di log

Rotazione dei file di log

Se si esgue lo script ripetutamente, i messaggi di log aggiuntivi sono accodati al file. Per creare ogni volta un nuovo file, si passa un parametro filemode a basicConfig() con il valore 'w'. Piuttosto che gestire in prima persona la dimensione del file è più semplice usare RotatingFileHandler:

import glob
import logging
import logging.handlers

LOG_FILENAME = 'logging_rotatingfile_example.out'

# Impostazione di un logger specifico con il livello di output desiderato
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

# Aggiunta dell'handler dei messaggi di log al logger
handler = logging.handlers.RotatingFileHandler(LOG_FILENAME,
                                               maxBytes=20,
                                               backupCount=5,
                                               )
my_logger.addHandler(handler)

# Registrazione di qualche messaggio
for i in range(20):
    my_logger.debug('i = %d' % i)

# Visualizzazione dei file che sono stati creati
logfiles = glob.glob('%s*' % LOG_FILENAME)
for filename in logfiles:
    print filename

Dovrebbero risultare 6 file separati, ognuno con una parte della storia delle registrazioni per l'applicazione:

$ python logging_rotatingfile_example.py
logging_rotatingfile_example.out
logging_rotatingfile_example.out.1
logging_rotatingfile_example.out.2
logging_rotatingfile_example.out.3
logging_rotatingfile_example.out.4
logging_rotatingfile_example.out.5

Il file con le registrazioni più recenti è sempre logging_rotatingfile_example.out, ed ogni volta che esso raggiunge il limite di dimensione viene rinominato con il suffisso .1.. Ognuno dei file di backup esistenti viene rinominato incrementando il suffisso (.1.) diventa .2. ecc.) mentre il file .5. viene eliminato.

Ovviamente questo esempio imposta una dimensione del file di log molto ridotta per estremizzare l'esempio. Si dovrà impostare maxBytes ad un valore appropriato.

Livelli di dettaglio

Un'altra utile caratteristica dell'API di logging è la capacità di produrre diversi messaggi per diversi livelli di log. Questo consente di fornire al proprio codice messaggi di debug, ad esempio; ma abbassando il livello di dettaglio del log questi messaggi di debug non sono scritti nel proprio sistema in produzione.

Livello,Valore
CRITICAL,50
ERROR,40
WARNING,30
INFO,20
DEBUG,10
UNSET,0

Le chiamate di logger, handler e messaggi di log possono ognuna specificare un livello. Il messaggio di log viene emesso solamente se l'handler ed il logger sono configurati per l'emissione di messaggi di quel livello o di quelli superiori. Ad esempio, se un messaggio è CRITICAL, ed il logger è impostato ad ERROR, il messaggio viene emesso (50 > 40). Se un messaggio è un WARNING, ed il logger viene impostato per produrre solo ERRORi, il messaggio non viene emesso (30 < 40).

import logging
import sys

LEVELS = { 'debug':logging.DEBUG,
            'info':logging.INFO,
            'warning':logging.WARNING,
            'error':logging.ERROR,
            'critical':logging.CRITICAL,
            }

if len(sys.argv) > 1:
    level_name = sys.argv[1]
    level = LEVELS.get(level_name, logging.NOTSET)
    logging.basicConfig(level=level)

logging.debug('Questo è un messaggio di debug')
logging.info('Questo + un messaggio di informazione')
logging.warning('Questo è un messaggio di avvertimento')
logging.error('Questo è un messaggio di errore')
logging.critical('Questo è un messaggio di errore critico')

Si esegua lo script con un parametro tipo 'debug' oppure 'warning' per vedere quali messaggi vengono mostrati ai livelli diversi

$ python logging_level_example.py debug
DEBUG:root:Questo è un messaggio di debug
INFO:root:Questo è un messaggio di informazione
WARNING:root:Questo è un messaggio di avvertimento
ERROR:root:Questo è un messaggio di errore
CRITICAL:root:Questo è un messaggio di errore critico

Il Log nelle Librerie

logging dovrebbe essere usato dagli sviluppatori di librerie piuttosto che di applicazioni. Per loro c'è perfino meno lavoro da fare. Occorre creare semplicemente una istanza di logger per ogni contesto, usando un nome appropriato, quindi registrare i messaggi usando i livelli standard. Fintanto che una libreria usa l'API di logging con consistente nomenclatura e selezione di livelli, l'applicazione può essere configurata per mostrare o nascondere i messaggi dalla libreria, a seconda delle necessità.

Nominare le Istanze di Logging

Si noterà che tutti i precedenti messaggi di log hanno 'root' incorporato in essi. Il modulo logging supporta una gerarchia di logger con nomi diversi. Un facile modo per determinare da dove provenga uno specifico messaggio di log è usare un oggetto logger separato per ciascuno modulo. Ogni nuovo logger eredita la configurazione del genitore, ed i messaggi di log inviati ad un logger includono il nome di quel logger. Opzionalmente, ogni logger può essere configurato diversamente, in modo che i messaggi dai diversi moduli vengono gestiti in diversi modi. Si osservi un semplice esempio di come eseguire il log da diversi moduli in modo da tracciare facilmente la sorgente dei messaggi:

import logging

logging.basicConfig(level=logging.WARNING)

logger1 = logging.getLogger('package1.module1')
logger2 = logging.getLogger('package2.module2')

logger1.warning('Questo messaggio proviene da un modulo')
logger2.warning('E questo messaggio proviene da un altro modulo')

E l'output:

$ python logging_modules_example.py
WARNING:package1.module1:Questo messaggio proviene da un modulo
WARNING:package2.module2:E questo messaggio proviene da un altro modulo

Ci sono moltissime altre opzioni per configurazre logging, incluse diverse opzioni di formattazione dei messaggi di log, la possibilità di invaire messaggi a destinazioni multiple, e la modifica della configurazione di un applicazione da tempo in esecuzione al volo usando un'interfaccia socket. Tutte queste opzioni sono trattate dettagliatamente nella documentazione della libreria del modulo.

Vedere anche:

logging
La documentazione della libreria standard per questo modulo.