Cet article couvre les bases de l’utilisation du module de journalisation standard livré avec toutes les distributions Python. Après l’avoir lu, vous devriez être en mesure d’intégrer facilement la journalisation dans votre application Python.
Module de journalisation de la bibliothèque standard
Python est livré avec un module de journalisation dans la bibliothèque standard qui fournit un cadre flexible pour émettre des messages de journalisation à partir de programmes Python. Ce module est largement utilisé par les bibliothèques et constitue le premier point d’accès pour la plupart des développeurs lorsqu’il s’agit de journalisation.
Le module fournit un moyen pour les applications de configurer différents gestionnaires de journal et un moyen de router les messages de journal vers ces gestionnaires. Cela permet une configuration très flexible qui peut traiter un grand nombre de cas d’utilisation différents.
Pour émettre un message de log, un appelant demande d’abord un logger nommé. Le nom peut être utilisé par l’application pour configurer différentes règles pour différents loggers. Ce logger peut ensuite être utilisé pour émettre des messages simplement formatés à différents niveaux de log (DEBUG, INFO, ERROR, etc.), qui peuvent à nouveau être utilisés par l’application pour traiter les messages de priorité supérieure différemment de ceux de priorité inférieure. Bien que cela puisse sembler compliqué, cela peut être aussi simple que cela:
import logginglog = logging.getLogger("my-logger")log.info("Hello, world")
En interne, le message est transformé en un objet LogRecord et acheminé vers un objet Handler enregistré pour ce logger. Le handler utilisera ensuite un Formatter pour transformer le LogRecord en une chaîne de caractères et émettre cette chaîne.
Heureusement, la plupart du temps, les développeurs n’ont pas à connaître ces détails. La documentation Python contient un excellent article sur le module de journalisation et comment tout cela fonctionne ensemble. Le reste de cet article se concentrera sur les meilleures pratiques plutôt que sur toutes les façons possibles d’utiliser ce module.
Niveaux de journalisation
Tous les messages de journalisation ne sont pas créés égaux. Les niveaux de journalisation sont répertoriés ici dans la documentation Python ; nous les inclurons ici à titre de référence. Lorsque vous définissez un niveau de journalisation en Python à l’aide du module standard, vous indiquez à la bibliothèque que vous souhaitez traiter tous les événements à partir de ce niveau. Si vous définissez le niveau de journalisation à INFO, il inclura les messages INFO, WARNING, ERROR et CRITICAL. Les messages NOTSET et DEBUG ne seront pas inclus ici.
Level | Valeur numérique |
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
Logging à partir de modules
Une application Python bienorganisée, une application Python est probablement composée de nombreux modules. Parfois, ces modules sont destinés à être utilisés par d’autres programmes, mais à moins que vous ne développiez intentionnellement des modules réutilisables à l’intérieur de votre application, il est probable que vous utilisiez des modules disponibles sur pypi et des modules que vous écrivez vous-même spécifiquement pour votre application.
En général, un module devrait émettre des messages de journalisation comme meilleure pratique et ne devrait pas configurer la façon dont ces messages sont traités. C’est la responsabilité de l’application.
La seule responsabilité des modules est de faciliter l’acheminement de leurs messages de journal par l’application. Pour cette raison, c’est une convention pour chaque module d’utiliser simplement un logger nommé comme le module lui-même. Cela permet à l’application d’acheminer facilement des modules différents, tout en gardant le code de journalisation du module simple. Le module a juste besoin de deux lignes pour configurer la journalisation, puis utiliser le logger nommé :
import logginglog = logging.getLogger(__name__)def do_something(): log.debug("Doing something!")
C’est tout ce qu’il y a à faire. En Python, __name__
contient le nom complet du module actuel, donc cela fonctionnera simplement dans n’importe quel module.
Configuration de la journalisation
Votre application principale doit configurer le sous-système de journalisation afin que les messages de journalisation aillent là où ils doivent aller. Le module de journalisation Python fournit un grand nombre de façons d’affiner cela, mais pour presque toutes les applications, la configuration peut être très simple.
En général, une configuration consiste à ajouter un Formatter
et un Handler
au logger racine. Comme cela est très courant, le module de journalisation fournit une fonction utilitaire appelée basicConfig
qui gère une majorité de cas d’utilisation.
Les applications devraient configurer la journalisation le plus tôt possible, de préférence en tant que première chose dans l’application, afin que les messages de journalisation ne soient pas perdus pendant le démarrage.
Enfin, les applications devraient envelopper un bloc try/except autour du code principal de l’application pour envoyer toute exception par l’interface de journalisation au lieu de simplement stderr
. C’est ce que l’on appelle un gestionnaire try catch global. Il ne doit pas être l’endroit où vous gérez toute votre journalisation des exceptions, vous devez continuer à planifier les exceptions dans des blocs try catch aux points nécessaires de votre code en règle générale.
Exemple 1 – Journalisation vers la sortie standard pour Systemd
C’est l’option la plus simple et probablement la meilleure pour configurer la journalisation de nos jours. Lorsque vous utilisez systemd pour exécuter un démon, les applications peuvent simplement envoyer des messages de journalisation à stdout ou stderr et faire en sorte que systemd transmette les messages à journald et syslog. En outre, il n’est même pas nécessaire d’attraper les exceptions, car Python les écrit déjà dans le fichier d’erreur standard. Cela dit, suivez la convention appropriée et gérez vos exceptions.
Dans le cas de l’exécution de Python dans des conteneurs comme Docker, la journalisation vers la sortie standard est aussi souvent la démarche la plus simple, car cette sortie peut être directement et facilement gérée par le conteneur lui-même.
import loggingimport oslogging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))exit(main())
C’est tout. L’application va maintenant enregistrer tous les messages de niveau INFO ou supérieur dans stderr
, en utilisant un format simple :
ERROR:the.module.name:The log message
L’application peut même être configurée pour inclure les messages DEBUG, ou peut-être seulement ERROR, en définissant la variable d’environnement LOGLEVEL
.
Le seul problème de cette solution est que les exceptions sont enregistrées sous forme de plusieurs lignes, ce qui peut poser des problèmes pour une analyse ultérieure. Malheureusement, la configuration de Python pour envoyer les exceptions multi-lignes comme une seule ligne n’est pas tout à fait aussi simple, mais certainement possible. Notez l’implémentation ci-dessous, l’appel à logging.exception
est un raccourci équivalent à logging.error(..., exc_info=True)
.
import loggingimport os class OneLineExceptionFormatter(logging.Formatter): def formatException(self, exc_info): result = super().formatException(exc_info) return repr(result) def format(self, record): result = super().format(record) if record.exc_text: result = result.replace("\n", "") return result handler = logging.StreamHandler()formatter = OneLineExceptionFormatter(logging.BASIC_FORMAT)handler.setFormatter(formatter)root = logging.getLogger()root.setLevel(os.environ.get("LOGLEVEL", "INFO"))root.addHandler(handler) try: exit(main())except Exception: logging.exception("Exception in main(): ") exit(1)
Exemple 2 – Syslog
L’alternative est de l’envoyer directement à syslog. C’est génial pour les anciens systèmes d’exploitation qui n’ont pas systemd. Dans un monde idéal, cela devrait être simple, mais malheureusement, Python nécessite une configuration un peu plus élaborée pour pouvoir envoyer des messages de journal unicode. Voici un exemple de mise en œuvre.
import loggingimport logging.handlersimport os class SyslogBOMFormatter(logging.Formatter): def format(self, record): result = super().format(record) return "ufeff" + result handler = logging.handlers.SysLogHandler('/dev/log')formatter = SyslogBOMFormatter(logging.BASIC_FORMAT)handler.setFormatter(formatter)root = logging.getLogger()root.setLevel(os.environ.get("LOGLEVEL", "INFO"))root.addHandler(handler) try: exit(main())except Exception: logging.exception("Exception in main()") exit(1)
Exemple 3 – Fichier de journalisation
La dernière option consiste à journaliser les messages directement dans un fichier. C’est rarement utile de nos jours, car les administrateurs peuvent configurer syslog pour écrire certains messages dans des fichiers spécifiques, ou si vous déployez à l’intérieur de conteneurs, c’est un anti-modèle. De plus, si vous utilisez une journalisation centralisée, le fait de devoir gérer des fichiers journaux supplémentaires est un problème supplémentaire. Mais c’est une option qui est toujours disponible.
Lorsque la journalisation vers des fichiers, la principale chose dont il faut se méfier est que les fichiers journaux doivent être tournés régulièrement. L’application doit détecter que le fichier journal est renommé et gérer cette situation. Bien que Python fournisse son propre gestionnaire de rotation de fichiers, il est préférable de laisser la rotation des journaux à des outils dédiés tels que logrotate. Le WatchedFileHandler
gardera la trace du fichier journal et le rouvrira en cas de rotation, ce qui le fait bien fonctionner avec logrotate sans nécessiter de signaux spécifiques.
Voici un exemple d’implémentation.
import loggingimport logging.handlersimport os handler = logging.handlers.WatchedFileHandler( os.environ.get("LOGFILE", "/var/log/yourapp.log"))formatter = logging.Formatter(logging.BASIC_FORMAT)handler.setFormatter(formatter)root = logging.getLogger()root.setLevel(os.environ.get("LOGLEVEL", "INFO"))root.addHandler(handler) try: exit(main())except Exception: logging.exception("Exception in main()") exit(1)
Autres destinations
Il est possible d’utiliser d’autres destinations de journal, et certains frameworks en font usage (par ex, Django peut envoyer certains messages de journal sous forme de courrier électronique). Le HTTPHandler
pourrait être utile lorsque vous vous exécutez dans un PaaS et que vous n’avez pas d’accès direct à l’hôte pour configurer syslog ou que vous êtes derrière un pare-feu qui bloque syslog sortant, et peut être utilisé pour journaliser directement vers des systèmes de journalisation centralisés comme SolarWinds® Loggly®.
Plusieurs des gestionnaires de journaux les plus élaborés de la bibliothèque de journalisation peuvent facilement bloquer l’application, provoquant des pannes simplement parce que l’infrastructure de journalisation ne répondait pas. Pour ces raisons, il est préférable de garder la configuration de la journalisation d’une application aussi simple que possible.
Une tendance croissante de la journalisation en général est de la séparer autant que possible de la fonctionnalité de base de votre application. De cette façon, vous pouvez avoir un comportement différent dans différents environnements ou contextes de déploiement. L’utilisation du HTTPHandler avec un système comme Loggly est un moyen simple de réaliser facilement cela directement dans votre application.
Lorsque vous déployez dans des conteneurs, essayez de garder les choses aussi simples que possible. Loguez vers out/err standard et comptez sur votre hôte de conteneur ou votre plateforme d’orchestration pour gérer la détermination de ce qu’il faut faire avec les logs. Vous pouvez toujours utiliser des services de centralisation des journaux, mais avec une approche sidecar ou log shipper.
Si une configuration à grain fin est souhaitable, le module de journalisation offre également la possibilité de charger la configuration de la journalisation à partir d’un fichier de configuration. Ceci est assez puissant, mais rarement nécessaire. Lors du chargement de la configuration de journalisation depuis un fichier, spécifiez disable_existing_loggers=False
. La valeur par défaut, qui n’est là que pour la compatibilité ascendante, désactivera tous les loggers créés par les modules. Cela casse de nombreux modules, donc à utiliser avec prudence.
Résumé
La journalisation en Python est simple et bien standardisée, grâce à un puissant cadre de journalisation directement dans la bibliothèque standard.
Les modules devraient simplement tout enregistrer dans une instance de logger pour leur nom de module. Cela permet à l’application de diriger facilement les messages de journalisation de différents modules vers différents endroits, si nécessaire.
Les applications ont ensuite plusieurs options pour configurer la journalisation. Dans une infrastructure moderne, cependant, suivre les meilleures pratiques simplifie beaucoup cela. À moins d’un besoin spécifique, le simple fait de journaliser vers stdout
stderr
et de laisser system
ou votre conteneur gérer les messages de journalisation est suffisant et constitue la meilleure approche.
Voir. Analysez-le. Inspectez-le. Résolvez-le
Voyez ce qui compte.
START FREE TRIAL
0 commentaire