logging - Segnala messaggi di stato, di errore e informativi

Scopo: Segnala messaggi di stato, di errore e informativi

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

Componenti di logging

Il sistema di registrazione è composto da quattro tipi di oggetti. Ogni modulo o applicazione che voglia registrare usa una istanza di Logger per aggiungere informazioni ai registri. La chiamata del logger crea un LogRecord, che viene usato per mantenere le informazioni in memoria fino a quando sono elaborate. Un Logger potrebbe avere un numero di oggetti Handler (gestori - n.d.t.) configurati la ricezione ed elaborazione dei record di registrazione. Handler usa l'oggetto Formatter per trasformare i record di registrazione in messaggi in uscita.

Registrazioni nelle Applicazioni contro Registrazioni nelle Librerie

Gli sviluppatori di applicazioni e gli autori di librerie possono entrambi utilizzare logging, ma ognuna di queste categorie ha diverse considerazioni da tenere a mente.

Gli sviluppatori dell'applicazione configurano il modulo logging, smistando i messaggi agli appropriati canali in uscita. E' possibile registrare messaggi con livelli di verbosità diversi oppure verso destinazioni diverse. Sono inclusi gestori per la scrittura dei messaggi di su file, verso indirizzi HTTP GET/POST, email tramite SMTP, socket generici oppure verso meccanismi di registrazione specifici di un sistema operativo ed è possibile creare classi di destinazione delle registrazioni personalizzate per particolari esigenze che non sono coperte dalle classi built-in.

Gli sviluppatori di librerie possono usare logging e avere anche meno lavoro da fare. Semplicemente possono creare una istanza di logger per ciascun contesto, usando un nome appropriato, quindi registrare i messaggi usando i livelli standard. Fintanto che una libreria usa l'API di registrazione con livelli e nomenclature consistenti, l'applicazione può essere configurata per mostrare o nascondere i messaggi dalla libreria, come si desidera.

Registrazione verso un File

La maggior parte delle applicazioni sono configurate per registrare verso un file. Si usi la funzione basicConfig() per impostare il gestore predefinito in modo che i messaggi di debug siano scritti a un file.

# logging_file_example.py

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')

with open(LOG_FILENAME, 'rt') as f:
    body = f.read()

print('FILE:')
print(body)

Terminata l'esecuzione dello script il messaggio di log viene scritto in logging_example.out :

$ python3 logging_file_example.py

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

Rotazione dei File di Registrazione

Se si esegue lo script ripetutamente, i messaggi di log aggiuntivi sono accodati al file. Per creare ogni volta che il programma viene eseguito un nuovo file, si passi un argomento filemode a basicConfig() con il valore 'w'. Piuttosto che gestire la creazione di file in questo modo, tuttavia, è meglio usare un RotatingFileHandler, che crea automaticamente nuovi file e preserva allo stesso tempo i vecchi file di registrazione.

# logging_rotatingfile_example.py

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 registrazione 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 sorted(logfiles):
    print(filename)

Il risultato è sei file distinti, ognuno con una parte della storia delle registrazioni per l'applicazione:

$ python3 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, e 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 a scopo dimostrativo. Si imposti maxBytes a un valore appropriato in un programma reale.

Livelli di Dettaglio

Un'altra utile caratteristica dell'API di logging è la capacità di produrre diversi messaggi per diversi livelli di registrazione. Questo significa che il codice può essere equipaggiato con messaggi di debug, ad esempio, e il livello di registrazione può essere impostato in modo che questi messaggi di debug non siano scritti in un sistema in produzione. La tabella seguente mostra i livelli di registrazione definiti nel modulo.

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

Il messaggio di registrazione viene emesso se il gestore e il logger sono configurati per l'emissione di messaggi di quel livello o superiore. Ad esempio, se un messaggio è CRITICAL, e il logger è impostato a ERROR, il messaggio viene emesso (50 > 40). Se un messaggio è un WARNING, e il logger viene impostato per produrre solo ERROR, il messaggio non viene emesso (30 < 40).

# logging_level_example.py

import logging
import sys

LEVELS = {
    'debug': logging.DEBUG,
    'info': logging.INFO,
    'avvertimento': logging.WARNING,
    'errore': logging.ERROR,
    'critico': 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 critico')

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

$ python3 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 critico
$ python3 logging_level_example.py info

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 critico

Nominare le Istanze di Logging

Tutti i precedenti messaggi di registrazione hanno 'root' (radice) incorporato in essi perchè il codice usa il logger radice. Un facile modo per identificare la provenienza di uno specifico messaggio di registrazione è quello di usare un oggetto logger separato per ciascun modulo. I messaggi di registrazione inviati a un logger includono il nome di quel logger. Ecco un esempio di come registrare da diversi moduli, in modo che sia facile rintracciare la sorgente del messaggio.

# logging_modules_example.py

import logging

logging.basicConfig(level=logging.WARNING)

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

logger1.warning('Questo messaggio viene da un modulo')
logger2.warning('Questo viene da un altro modulo')

Il risultato mostra i diversi nomi di modulo per ciascuna riga in uscita.

$ python3 logging_modules_example.py

WARNING:package1.module1:Questo messaggio viene da un modulo
WARNING:package2.module2:Questo viene da un altro modulo

L'alberatura del Logging

Le istanze di Logger sono configurate come una struttura ad albero, in base ai loro nomi, come illustrato nella figura seguente. Tipicamente, ogni applicazione o libreria definisce un nome base, impostando come figli i logger dei moduli individuali. Il logger radice non ha nome.

alberatura logger.

La struttura ad albero è utile per configurare le registrazioni in quanto implica che ciascun logger non deve avere bisogno del proprio insieme di gestori. Nel caso in cui un logger non abbia gestori, l'elaborazione del messaggio viene eseguita dal suo genitore. Questo vuol dire che per molte applicazioni è necessario configurare solamente i gestori sul logger radice, e tutte le informazioni da registrare vengono raccolte e inviate allo stesso posto, come mostra la seguente figura.

gestore con un logging.

La struttura ad albero consente anche l'impostazione di diversi livelli di verbosità, di gestori e di formattatori per diverse parti di una applicazione o libreria per controllare quali messaggi debbano essere registrati e quali ignorati, come da seguente figura.

diversi livelli e gestori.

Integrazione con il modulo Warnings

Il modulo logging si integra con il modulo warnings tramite captureWarnings(), che configura warnings per inviare messaggi tramite il sistema di registrazione invece che esporli direttamente.

# logging_capture_warnings.py

import logging
import warnings

logging.basicConfig(
    level=logging.INFO,
)

warnings.warn('Questo avvertimento non viene registrato')

logging.captureWarnings(True)

warnings.warn('Questo avvertimento viene registrato')

Il messaggio di avvertimento viene inviato a un logger chiamato py.warnings usando il livello WARNING.

$ python3 logging_capture_warnings.py

logging_capture_warnings.py:10: UserWarning: Questo avvertimento non viene registrato
  warnings.warn('Questo avvertimento non viene registrato')
WARNING:py.warnings:logging_capture_warnings.py:14: UserWarning: Questo avvertimento viene registrato
  warnings.warn('Questo avvertimento viene registrato')

Vedere anche:

logging
La documentazione della libreria standard per questo modulo è estensiva, e include tutorial e materiale di riferimento che va oltre gli esempi qui presentati.
Note di portabilità da Python 2 a Python 3 per logging
warnings
Avvertimenti non fatali
logging_tree
Pacchetto di terze parti di Brandon Rhodes per mostrare l'albero di logging per una applicazione.
Logging cookbook
Parte della documentazione della libreria standard, con esempi di uso di logging per diversi compiti.