8.8 KiB
title, author
title | author |
---|---|
Journalisation d'événements | Steve Kossouho |
Journaliser des événements en Python
Journaliser ?
Lorsque l'on exécute un programme au long cours, souvent un service, un serveur ou même une application graphique, il est souvent important de pouvoir retracer ce qui s'est produit avant qu'une exception soit levée, ou qu'un comportement inattendu montre le bout de son nez.
La journalisation est un outil formidable à cet effet, puisqu'elle vous permet de consigner, dans la console ou tout autre support, une liste chronologique de messages et d'événements que vous pouvez retracer a posteriori. Pour peu que vous consigniez assez de messages, ils peuvent être capitaux, ne serait-ce que pour fournir du support à vos utilisateurs.
À quoi ça ressemble ?
La journalisation est sensiblement identique sur tous les systèmes. Vous consignez des messages avec des niveaux de sévérité divers.
En voici un exemple arbitraire :
2021-01-01 23:51:15 - DEBUG - Logging message 3
2021-01-01 23:51:10 - DEBUG - Logging message 2
2021-01-01 23:50:37 - WARNING - Logging message 1
Un administrateur ou un outil peuvent consulter ce genre de fichier pour en savoir plus sur leur système. En marquant les messages comme possédant un niveau de sévérité, il est aussi possible de voir rapidement quels messages doivent être traités en priorité.
Les niveaux de sévérité d'un système de journalisation sont les suivants :
DEBUG
: Pour les développeurs, remplace leprint
.INFO
: Information standard (version, action effectuée etc.)WARNING
: Attention à porter à un problème potentiel.ERROR
: Une erreur s'est produite, gênante pour les utilisateurs.CRITICAL
: Une erreur irrécupérable à corriger immédiatement s'est produite.
Le module logging
La bibliothèque standard de Python propose un module, qui semble inspiré d'une API C++ ou Java (typographie) et qui offre les outils nécessaires à organiser une bonne journalisation.
import logging
logger = logging.getLogger(__name__)
Récupère le logger portant le nom du module en cours. Attention, si le module est celui que vous avez lancé spécifiquement, la variable
__name__
(ou l'attribut __name__
du module) prendra la valeur "__main__"
.
import logging
logger = logging.getLogger(__name__)
logger.log(logging.WARNING, "Message.")
logger.warning("Same message as before.")
Consignation de nouveaux messages.
Système de loggers
Un logger est un objet que vous utilisez pour consigner de nouveaux messages dans votre système. Cet objet doit être associé à un ou plusieurs objets de type handler
, qui sont des canaux de sortie pour le logger.
Hiérarchie des loggers
Les loggers portent tous un nom. Le système de journalisation de Python fonctionne avec un système de hiérarchie, où le nom d'un logger définit où il se trouve dans la hiérarchie. Un message consigné par un logger est transmis à son logger parent, jusqu'au logger racine.
Dans l'exemple précédent,
- Un message consigné avec le logger nommé
data.excel
remonte jusqu'au au logger racine. - Tous les handlers associés aux loggers rencontrés en remontant la hiérarchie sont utilisés pour traiter le message.
- Si aucun handler n'a été rencontré après être arrivé au sommet, le handler de dernier recours (
lastResort
) est utilisé pour consigner l'événement. - Ce handler affiche les messages de sévérité
>= logging.WARNING
{.python} dans la console d'erreur (rouge).
Utiliser les handlers
Le clou du spectacle consiste à configurer des handlers pour les associer à des loggers. Pour configurer correctement un handler, il nous faut définir :
- Son type (sortie fichier, console);
- Le format textuel des événements consignés;
- Son niveau de sévérité minimum pour accepter des messages.
Configuration d'un handler
import logging
handler = logging.FileHandler("filename.log", encoding="utf-8")
# Chaîne de formatage des événements
formatting = logging.Formatter("%(asctime)s - %(message)s")
handler.setFormatter(formatting)
# Le handler acceptera tous les messages >= DEBUG
handler.setLevel(logging.DEBUG)
Référence des spécifieurs pour le format de texte
Types de handlers
Les types de handlers disponibles sont disponibles ici :
logging.FileHandler
: sérialise dans un fichier (documentation).logging.StreamHandler
: sérialise dans un flux, généralement console (voir slide suivant).logging.handlers.*
: handlers plus avancés, dont fichiers avec rotation.
Types de flux console
Pour les handlers vers un flux, voici la liste des flux classiques disponibles :
sys.stdout
: sortie console normale.sys.stderr
: sortie console d'erreur (affiché en rouge).
(voir le slide suivant pour l'utilisation de ces flux)
Ajouter un handler à un logger
Une fois un handler configuré, vous pouvez l'associer à un logger (puisque c'est sur le logger que vous allez utiliser des méthodes de journalisation).
import logging
import sys
logger = logging.getLogger("logger.name")
handler = logging.StreamHandler(sys.stdout)
# Imaginons que l'on configure notre handler correctement
# (ici, il manque un format à spécifier)
# On peut *ajouter* notre handler à notre logger
logger.addHandler(handler)
Un logger peut également définir son propre niveau minimal de sévérité, qui sera utilisé pour potentiellement limiter la portée de ses handlers.
import logging
import sys
logger = logging.getLogger("logger.name")
handler = logging.StreamHandler(sys.stdout) # il faudrait spécifier un format
logger.addHandler(handler)
# Limiter tous les handlers associés au niveau WARNING
logger.setLevel(logging.WARNING)
Le logger racine
Le logger racine est souvent utilisé pour configurer une journalisation globale pour toute votre application, puisque tout logger remonte toujours ses messages vers celui-ci.
Pour le configurer, utilisez la variable root
du module logging
:
import logging
# Exemple minimaliste
handler = logging.FileHandler("filename")
# Tous les loggers vont passer par ce handler
logging.root.addHandler(handler)
Récapitulatif minimal
import logging
# Les 3 types d'objets nécessaires à la configuration
logger = logging.getLogger("myname") # nouveau logger nommé "myname"
handler = logging.FileHandler("myfile.log", encoding="utf-8") # handler fichier
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") # format de messages
# Configuration du handler
handler.setFormatter(formatter) # configurer le handler
handler.setLevel(logging.DEBUG) # niveau de sévérité minimum
# Configuration du logger
logger.addHandler(handler) # ajouter le handler au logger
logger.setLevel(logging.INFO) # niveau de sévérité minimum du logger
Exemple de logger à deux handlers
Plus : Fichier de configuration
On peut configurer sa journalisation et créer d'une traite un ensemble de loggers
via un fichier
de configuration (il est possible d'utiliser un dictionnaire), qui
sera stocké sous la forme d'un fichier ini. Le contenu à insérer dans ce fichier est décrit dans la section
Configuration file format
de la documentation Python.
Les fichiers ini ont une structure qui est toujours de la forme :
[section1]
property1 = value
property2 = value
[section2]
property3 = value
Charger un fichier de configuration
Lorsque vous avez défini un fichier de configuration avec tous ses loggers
, handlers
et
formatters
, vous pouvez l'utiliser via la fonction logging.config.fileConfig()
:
from logging.config import fileConfig
# Initialiser des loggers depuis une fonction
# Le fichier devrait être stocké dans un dossier à l'arborescence bien
# choisie.
fileConfig("logging.conf.ini", disable_existing_loggers=False, encoding="utf-8")