logging en Python : garder une trace de ce que fait un programme

logging garde une trace horodatée de ce qu'un programme fait pendant qu'il tourne, avec des niveaux de gravité que l'on peut filtrer sans toucher au code.
5 min de lecture
Believemy logo

Définition

Un programme qui tourne sans personne devant l'écran finit tôt ou tard par rencontrer un cas imprévu. Le savoir arrive après coup : sans trace écrite pendant l'exécution, il ne reste que le résultat final, qui ne raconte jamais l'histoire qui y a mené.

logging est le module de la bibliothèque standard qui écrit cette histoire à mesure que le programme avance. Chaque message part avec une date, un niveau de gravité et le nom de l'endroit d'où il vient, puis atterrit là où la configuration l'envoie : terminal, fichier, service de collecte. Le code dit ce qui s'est passé, la configuration décide qui l'entend.

PYTHON
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info("Import démarré, %s lignes à traiter", 1200)
logger.warning("Ligne 47 ignorée, date illisible")

Le __name__ passé à getLogger n'est pas un détail de style : il colle au message sa provenance exacte, et permettra plus tard de faire taire un fichier bavard sans faire taire les autres.


Le premier réflexe, en écrivant du code, est d'ajouter un print pour voir passer une valeur, ce qui marche tant que quelqu'un regarde le terminal. Une fois le programme livré, ces print deviennent du bruit permanent, ou des messages qu'on aurait voulu garder.

logging reprend le même geste, mais ajoute ce qu'un print ne sait pas faire seul : une date automatique, un niveau filtrable après coup, une destination qu'on peut changer sans toucher au code. Voici les deux côte à côte :

BesoinAvec printAvec logging
Voir une valeur pendant qu'on écrit le codeParfaitInutilement lourd
Savoir à quelle heure la chose est arrivéeÀ écrire soi-mêmeHorodatage automatique
Couper le bavardage en productionSupprimer les lignesRelever un seuil
Écrire dans un fichierRediriger le terminalAjouter un handler
Garder la pile d'un plantageÀ la mainUne seule méthode

Il existe des cas où logging n'apporte rien, un script de trente lignes par exemple. Plus important : un programme qui affiche son résultat doit utiliser print, jamais logging, le premier répondant à la personne qui attend, le second racontant en coulisses ce que le programme fait.


Les cinq niveaux

Écrire un message à chaque étape semble une bonne idée, jusqu'à ce que le terminal déborde et que l'important se noie dans le détail. logging répond en attachant un niveau à chaque message, comparé à un seuil configuré : seuls les messages qui l'atteignent s'affichent.

Voici les cinq niveaux, du plus courant au plus grave :

NiveauCe qu'il raconte
DEBUGLe détail utile quand on cherche un bug, invisible le reste du temps
INFOUne étape normale : le traitement démarre, le fichier est écrit
WARNINGUne anomalie que le programme a su contourner
ERRORUne opération a échoué, le programme continue
CRITICALLe programme ne peut plus continuer

Le seuil par défaut vaut WARNING. Un programme tout juste configuré n'affiche donc ni ses DEBUG ni ses INFO, ce qui surprend presque tout le monde la première fois.


Le piège du premier jour

« J'appelle logger.info et rien ne s'affiche. » C'est cette même surprise du seuil : le message n'est pas perdu, il est filtré. La correction tient en un appel, une seule fois, au point d'entrée du programme, avant tout autre message :

PYTHON
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s : %(message)s",
)

Cet appel cache une deuxième surprise, plus sournoise : basicConfig ne fait rien si la journalisation est déjà configurée, et un premier message émis avant l'appel la configure par défaut, sans prévenir. Un appel trop tardif est donc ignoré en silence.

Attention

Pour qui écrit une bibliothèque destinée à d'autres, la règle est inverse : n'appelez jamais basicConfig à l'intérieur, sous peine d'imposer votre format au programme qui l'installe.


Enregistrer une erreur sans perdre le traceback

Dans un bloc except, le réflexe est de recopier le message de l'exception et de passer à la suite. Cela jette pourtant le plus utile : le traceback, qui dit à quelle ligne le problème est né. Sans lui, il faut reconstituer ce chemin de mémoire, ou reproduire le bug pour le revoir.

La méthode exception l'attache toute seule, et se réserve aux blocs de rattrapage :

PYTHON
try:
    unitaire = facture["montant"] / facture["quantite"]
except ZeroDivisionError:
    logger.exception("Quantité nulle sur la facture %s", facture["id"])
    unitaire = 0

Notez la virgule, là où une f-string aurait été tentante. Le message reste un gabarit constant, ce qui laisse un outil de collecte regrouper mille occurrences d'un même incident au lieu de mille lignes distinctes. Le gain de performance parfois mis en avant reste négligeable hors des boucles très chaudes.


Questions fréquentes

Question

Faut-il un logger par fichier ou un seul pour tout le programme ?

Un par fichier, toujours avec la même ligne en haut : logger = logging.getLogger(__name__). Les loggers forment une hiérarchie fondée sur les points du nom, si bien que boutique.paiement hérite des réglages de boutique, ce qui permet de passer un seul module en DEBUG sans noyer le reste.

Question

Faut-il écrire les journaux dans un fichier ?

Cela dépend surtout d'où le programme tourne. Sur un serveur classique, oui, avec un handler qui découpe le fichier au-delà d'une certaine taille, faute de quoi il devient illisible. Dans un conteneur, non : la sortie standard suffit, l'hébergeur ramasse et archive, et c'est ainsi que se déploie d'ordinaire une application FastAPI ou Django.

Question

Que ne faut-il jamais écrire dans un journal ?

Un mot de passe, un jeton, un numéro de carte, le contenu complet d'un formulaire. Un journal se conserve des mois et finit lu par des personnes sans accès à la base de données. Enregistrez plutôt l'identifiant de l'utilisateur concerné que ses données, et évitez d'écrire une réponse json entière sans savoir précisément ce qu'elle contient.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.