Vous êtes ici : Accueil Tutoriels Utiliser des fichiers de logs

Utiliser des fichiers de logs

Lorsque l'on développe une application, il est important de penser à générer des traces dans un fichier de log adapté. Pour cela, Python dispose d'un module parfaitement adapté nommé logging. Ce tutoriel en fait une courte présentation.

Comment utiliser un fichier de log

Le meilleur exemple d'utilisation d'un fichier de log est celui d'Apache2. En effet, lorsque l'on fait du développement web (avec Python + mod_wsgi, par exemple), toutes les erreurs de type 5xx engendrent une écriture dans un fichier de log. On peut également y trouver des informations secondaites (notices) ou des avertissements (warnings) pour signaler une erreur de configuration par exemple.

Pour lire ce fichier, il suffit d'utiliser dans un terminal utilisateur la commande suivante :

$ tail -f /var/log/apache2/error.log

Pour les non-initiés, la commande tail permet d'afficher dans le terminal les quelques dernières lignes du fichier de log et l'option -f fait que le programme se met en attente et que dès qu'une nouvelle écriture sera faite dans le fichier de log, elle sera également affichée dans le terminal.

Il est possible de rajouter quelques lignes dans le terminal pour séparer visuellement ce qui est déjà écrit de ce qui va être généré lors de la prochaine écriture dans le fichier de log. Celles-ci ne sont ajoutées que dans le terminal, le fichier de log n'étant pas modifié.

On peut alors jongler entre un navigateur web et le terminal affichant les logs. Ceci est idéal lorsque l'on travaille sur une plateforme de développement, avec un apache installé sur son propre poste et en étant seul à faire des requêtes.

Mais au delà de cette utilisation confortable, le fichier de log peut également être utilisé dans un contexte ou plusieurs personnes exécutent des requêtes simultanément. Il devient alors nécessaire de sélectionner uniquement les lignes qui sont générées par nos propres requêtes. On va donc utiliser la puissance du shell de Linux pour faire cette sélection (en imaginant que notre client web s'exécute depuis une machine dont l'IP est 192.168.1.1) :

$ tail -f /var/log/apache2/error.log | grep 192.168.1.1

On peut également vouloir n'afficher que les erreurs du niveau ERROR (donc ne pas voir les warnings, notices, ...) :

$ tail -f /var/log/apache2/error.log | grep 192.168.1.1 | grep ERROR

On voit donc que l'utilisation d'un fichier de log peut être confortable à condition de bien connaître ces commandes shell.

Générer des log facilement utilisables

Généralités

Créer un logger est d'une facilité déconcertante :

>>> import logging
>>> logger = logging.getLogger()

Ce qui compte réellement, c'est de générer des logs facilement utilisables. En effet, lorsque l'on connaît les astuces auquelles il faut requérir pour lire confortablement un fichier de log, on voit immédiatement l'intérêt de formatter correctement chaque ligne, de manière à les sélectionner à l'aide d'un grep adapté.

Niveau d'erreur

Il est utile de logger plusieur niveau d'erreur. Ceci permet de différencier les messages utiles à la correction d'erreurs qui seront enregistrés même en production de ceux relatif au simple debug qui ne seront enregistré qu'en mode développement. Voici les niveaux définis :

  • CRITICAL : le programme ne peut pas continuer son exécution ;
  • ERROR : le programme a rencontré une erreur, l'a géré en adoptant une solution de contournement, mais n'a pas pu effectuer une des actions demandées  il est nécessaire de la corriger car c'est un bug(dans une boucle except d'une exception) ;
  • WARNING : le programme a trouvé un problème potentiel, mais cela ne dérange pas le cours normal de son exécution ; le développeur doit cependant être attentif à l'erreur puisqu'elle pourrait expliquer un dysfonctionnement ultérieur) ;
  • INFO : le programme donne des informations sur ce qu'il est en train de faire et tout se déroule comme prévu ;
  • DEBUG : le programme donne des informations de nature à aider le développeur à réaliser des diagnostics ; ces informations peuvent être nombreuses ;

Ainsi, lorsque l'on développe une application, on pourra écrire des logs de chaque niveau via les instructions suivantes :

logger.critical("Ceci est un message critique (entraîne généralement l'arrêt de l'exécution du programme)")
logger.error("Ceci est un message d'erreur (indique un bug à corriger)")
logger.warning("Ceci est un message d'avertissement (indique un problème potentiel)")
logger.info("Ceci est un message d'information (indique que tout se passe bien en donnant des informations)")
logger.debug("Ceci est un message de débogage (informations aidant au diagnostics)")

On peut mettre des messages de toute nature à tout endroit de l'application, en cohérence avec leurs niveaux respectifs et il n'est pas nécessaire de les supprimer avant une mise en production pour n'afficher que certains niveaux. En effet, le logger se configure de manière à ne logger que certains niveaux :

logger.setLevel(logging.DEBUG)

Le principe est de dire quel est le niveau le plus bas auquel il faut descendre (les niveaux au-dessus seront également affichés. Ainsi, la ligne précédente permet d'afficher tous les niveaux alors que la ligne suivante permet de n'afficher que les erreurs critiques et les erreurs :

logger.setLevel(logging.ERROR)

Formattage des lignes

On a vu que le recours à la commande grep est très utile pour trier les lignes du fichier de log à afficher. Il faut donc se construire un début de ligne qui nous permette d'utiliser cette fonctionnalité :

formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

Exemples de configurations

Configuration détaillée 

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh = logging.FileHandler('myapplication.log')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
logger.addHandler(fh)

Configuration simplifiée 

import logging
logging.basicConfig(filename='myapplication.log', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

Configuration avec plusieurs fichiers de log 

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh1 = logging.FileHandler('myapplication-all.log')
fh1.setLevel(logging.DEBUG)
fh1.setFormatter(formatter)
logger.addHandler(fh1)
fh2 = logging.FileHandler('myapplication-info.log')
fh2.setLevel(logging.INFO)
fh2.setFormatter(formatter)
logger.addHandler(fh2)
fh3 = logging.FileHandler('myapplication-error.log')
fh3.setLevel(logging.ERROR)
fh3.setFormatter(formatter)
logger.addHandler(fh3)

Configuration pour écrire également dans la sortie standard 

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh = logging.FileHandler('log_filename.txt')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
logger.addHandler(fh)
sh = logging.StreamHandler()
sh.setLevel(logging.DEBUG)
sh.setFormatter(formatter)
logger.addHandler(sh)

Conclusion

Ce mini-tuto vous aura présenté plusieurs manières d'utiliser des fichiers de log, adaptées à des situations différentes. Pour aller plus loin, sachez qu'il existe des manières plus simples d'utiliser le module logging et les loggers. La documentation officielle est .

Mots-clés associés : ,
Spinner