Tutoriel de journalisation Python

  • par

Dernière modification le 6 juillet 2020

Le tutoriel de journalisation Python montre comment faire de la journalisation en Python avec le module de journalisation.

La journalisation

La journalisation est le processus d’écriture d’informations dans des fichiers journaux. Les fichiers journauxcontiennent des informations sur divers événements qui se sont produits dans le système d’exploitation, les logiciels ou dans la communication.

Objectif de la journalisation

La journalisation est effectuée aux fins suivantes :

  • Recueil d’informations
  • Dépannage
  • Génération de statistiques
  • Audit
  • Profilage

La journalisation ne se limite pas à identifier les erreurs dans le développement de logiciels. Elle est également utilisée pour détecter les incidents de sécurité, surveiller les violations de politique, fournir des informations en cas de problème, trouver les goulets d’étranglement des applications ou générer des données d’utilisation.

Quels événements consigner

Les événements qui doivent être consignés comprennent les échecs de validation des entrées, les échecs d’authentification et d’autorisation, les erreurs d’application, les changements de configuration et les démarrages et arrêts d’application.

Quels événements ne doivent pas être journalisés

Les événements qui ne doivent pas être journalisés incluent le code source de l’application, les valeurs d’identification de session,les jetons d’accès, les données personnelles sensibles, les mots de passe, les chaînes de connexion à la base de données, les clés de chiffrement, les données relatives aux comptes bancaires et aux détenteurs de cartes.

Bonnes pratiques de journalisation

Voici quelques bonnes pratiques pour effectuer la journalisation :

  • La journalisation doit être significative.
  • La journalisation doit contenir le contexte.
  • La journalisation doit être structurée et effectuée à différents niveaux.
  • La journalisation doit être équilibrée ; elle ne doit pas inclure trop ou trop peu d’informations.
  • Les messages de journalisation doivent être compréhensibles pour les humains et analysables par les machines.
  • La journalisation dans les applications plus complexes doit être effectuée dans plusieurs fichiers journaux.
  • La journalisation devrait être adaptée au développement et à la production.

Le module de journalisation

Le module de journalisation de Python définit des fonctions et des classes quiimplémentent un système flexible de journalisation d’événements pour les applications et les bibliothèques.

Les composants du module de journalisation

Le module de journalisation a quatre composants principaux : les loggers, les handlers, les filtres et les formateurs. Les loggers exposent l’interface que le code d’application utilise directement.Les handlers envoient les enregistrements de log (créés par les loggers) vers la destination appropriée.Les filtres fournissent une facilité à grain plus fin pour déterminer quels enregistrements de log à sortir.Les formateurs spécifient la disposition des enregistrements de log dans la sortie finale.

Hiérarchie de journalisation de Python

Les loggers de Python forment une hiérarchie. Un logger nommé main est un parent de main.new.

Les loggers enfants propagent les messages jusqu’aux handlers associés à leurs loggers ancêtres. De ce fait, il est inutile de définir etconfigurer des handlers pour tous les loggers de l’application. Il issuffit de configurer des handlers pour un logger de niveau supérieur et de créer des childloggers selon les besoins.

Niveaux de journalisation Python

Les niveaux sont utilisés pour identifier la gravité d’un événement. Il existe six niveaux de journalisation :

  • CRITIQUE
  • ERROR
  • WARNING
  • INFO
  • DEBUG
  • NOTSET

Si le niveau de journalisation est défini sur WARNING, tous les messages WARNINGERROR, et CRITICAL sont écrits dans le fichier journalou la console. S’il est défini à ERROR, seuls les messages ERROR etCRITICAL sont enregistrés.

Loggers ont un concept de niveau effectif. Si un niveau n’est pas explicitement défini sur un logger, le niveau de son parent est utilisé à la place comme son niveau effectif. Si le parent n’a pas de niveau explicite défini, son parent estexaminé, et ainsi de suite – tous les ancêtres sont recherchés jusqu’à ce qu’un niveau explicitement défini soit trouvé.

Lorsque le logger est créé avec getLogger(), le niveau est défini àNOTSET. Si le niveau de journalisation n’est pas défini explicitement avec setLevel(),les messages sont propagés aux parents du logger. La chaîne des loggers ancêtres du logger est parcourue jusqu’à ce que soit un ancêtre avec un niveau autre que NOTSET soit trouvé,soit la racine soit atteinte. Le logger racine a un niveau par défaut WARNING défini.

Root logger

Tous les loggers sont des descendants du logger racine. Chaque logger transmet les messages de log à son parent. Les nouveaux loggers sont créés avec la getLogger(name)méthode. L’appel de la fonction sans nom (getLogger()) renvoie le logger racine.

Le logger racine a toujours un niveau explicite défini, qui est WARNINGpar défaut.

Le loogger racine se trouve au sommet de la hiérarchie et est toujours présent, même s’il n’est pas configuré. En général, le programme ou la bibliothèque ne devrait pas journaliser directement contre le logger racine. Au lieu de cela, un logger spécifique pour le programme doit être configuré. Le logger racine peut être utilisé pour activer et désactiver facilement tous les loggers de toutes les bibliothèques.

Python logging simple example

Le logging module possède des méthodes simples qui peuvent être utilisées d’emblée sans aucune configuration. Cela peut être utilisé pour une journalisation simple.

simple.py
#!/usr/bin/env pythonimport logginglogging.debug('This is a debug message')logging.info('This is an info message')logging.warning('This is a warning message')logging.error('This is an error message')logging.critical('This is a critical message')

L’exemple appelle cinq méthodes du module logging.Les messages sont écrits dans la console.

$ simple.pyWARNING:root:This is a warning messageERROR:root:This is an error messageCRITICAL:root:This is a critical message

Vous remarquerez que le logger root est utilisé et que seuls trois messages ont été écrits.Cela s’explique par le fait que par défaut, seuls les messages de niveau warning et supérieur sont écrits.

Python set logging level

Le niveau de journalisation est défini avec setLevel().Il fixe le seuil de ce logger à lvl.Les messages de journalisation qui sont moins sévères que lvl seront ignorés.

set_level.py
#!/usr/bin/env pythonimport logginglogger = logging.getLogger('dev')logger.setLevel(logging.DEBUG)logger.debug('This is a debug message')logger.info('This is an info message')logger.warning('This is a warning message')logger.error('This is an error message')logger.critical('This is a critical message')

Dans l’exemple, nous changeons le niveau de journalisation en DEBUG.

logger = logging.getLogger('dev')

La getLogger() renvoie un logger avec le nom spécifié.Si le nom est None, elle renvoie le logger racine. Le nom peut être une chaîne séparée par des points définissant la hiérarchie de journalisation ; par exemple ‘a’, ‘a.b’, ou ‘a.b.c’. Notez qu’il existe un nom racine implicite, qui n’est pas affiché.

$ set_level.pyThis is a warning messageThis is an error messageThis is a critical message

Maintenant tous les messages ont été écrits.

Niveau de journalisation effectif de Python

Le niveau de journalisation effectif est le niveau défini explicitement ou déterminé à partir des parents du logger.

effective_level.py
#!/usr/bin/env pythonimport loggingmain_logger = logging.getLogger('main')main_logger.setLevel(5)dev_logger = logging.getLogger('main.dev')print(main_logger.getEffectiveLevel())print(dev_logger.getEffectiveLevel())

Dans l’exemple, nous examinons le niveau de journalisation effectif de deux loggers.

dev_logger = logging.getLogger('main.dev')

Le niveau du dev_logger n’est pas défini ; le niveau de son parent est alors utilisé.

$ effective_level.py55

Voici la sortie.

Python logging handlers

Handler est un objet responsable de l’envoi des logmessages appropriés (en fonction de la gravité des messages de log) vers la destination spécifiée par le handler.

Les handlers sont propagés comme les niveaux. Si le logger n’a pas dehandler défini, sa chaîne d’ancêtres est recherchée pour un handler.

handlers.py
#!/usr/bin/env pythonimport logginglogger = logging.getLogger('dev')logger.setLevel(logging.INFO)fileHandler = logging.FileHandler('test.log')fileHandler.setLevel(logging.INFO)consoleHandler = logging.StreamHandler()consoleHandler.setLevel(logging.INFO)logger.addHandler(fileHandler)logger.addHandler(consoleHandler)logger.info('information message')

L’exemple crée deux handlers pour un logger : un handler de fichier et un handler de console.

fileHandler = logging.FileHandler('test.log')

FileHandler envoie les enregistrements du journal au test.logfichier.

consoleHandler = logging.StreamHandler()

StreamHandler envoie les enregistrements de journal à un flux.Si le flux n’est pas spécifié, le sys.stderr est utilisé.

logger.addHandler(fileHandler)

Le gestionnaire est ajouté au logger avec addHandler().

Python logging formatters

Formatter est un objet qui configure l’ordre final,la structure et le contenu de l’enregistrement du log. En plus de la chaîne de message, les enregistrements de journal incluent également la date et l’heure, les noms de journal et la gravité du niveau de journal.

formatter.py
#!/usr/bin/env pythonimport logginglogger = logging.getLogger('dev')logger.setLevel(logging.INFO)consoleHandler = logging.StreamHandler()consoleHandler.setLevel(logging.INFO)logger.addHandler(consoleHandler)formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')consoleHandler.setFormatter(formatter)logger.info('information message')

L’exemple crée un logger de console et ajoute un formatter à sonhandler.

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

Le formatter est créé. Il comprend la date et l’heure, le nom du logger, le nom du niveau de journalisation et le message du log.

consoleHandler.setFormatter(formatter)

Le formateur est défini sur le handler avec setFormatter().

$ formatter.py2019-03-28 14:53:27,446 dev INFO: information message

Le message avec le format défini est affiché dans la console.

Python logging basicConfig

La basicConfig() configure le logger racine. Elle effectue la basicconfiguration du système de journalisation en créant un gestionnaire de flux avec un formateur par défaut. Les debug()info()warning()error() et critical() appellentbasicConfig() automatiquement si aucun gestionnaire n’est défini pour le logger racine.

basic_config.py
#!/usr/bin/env pythonimport logginglogging.basicConfig(filename='test.log', format='%(filename)s: %(message)s', level=logging.DEBUG)logging.debug('This is a debug message')logging.info('This is an info message')logging.warning('This is a warning message')logging.error('This is an error message')logging.critical('This is a critical message')

L’exemple configure le logger racine avec basicConfig.

logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s', level=logging.DEBUG)

Avec filename, nous définissons le fichier dans lequel nous écrivons les messages du journal.La format détermine ce qui est enregistré dans le fichier ; nous avonsle nom du fichier et le message. Avec level, nous définissons le seuil de journalisation.

$ basic_config.py$ cat test.logbasic_config.py: This is a debug messagebasic_config.py: This is an info messagebasic_config.py: This is a warning messagebasic_config.py: This is an error messagebasic_config.py: This is a critical message

Après avoir exécuté le programme, nous avons cinq messages écrits dans le fichiertest.log.

Fichier de journalisation PythonConfig

La fileConfig() lit la configuration de journalisation à partir d’un fichier au format configparser.

log.conf
keys=root,devkeys=consoleHandlerkeys=extend,simplelevel=INFOhandlers=consoleHandlerlevel=INFOhandlers=consoleHandlerqualname=devpropagate=0class=StreamHandlerlevel=INFOformatter=extendargs=(sys.stdout,)format=%(asctime)s - %(name)s - %(levelname)s - %(message)sformat=%(asctime)s - %(message)s

La log.conf définit un logger, un handler et un formatter.

file_config.py
#!/usr/bin/env pythonimport loggingimport logging.configlogging.config.fileConfig(fname='log.conf')logger = logging.getLogger('dev')logger.info('This is an information message')

L’exemple lit le fichier de configuration de journalisation à partir du log.conf.

$ file_config.py2019-03-28 15:26:31,137 - dev - INFO - This is an information message

Voici la sortie.

Variable de journalisation Python

Les données dynamiques sont journalisées en utilisant le formatage des chaînes de caractères.

log_variable.py
#!/usr/bin/env pythonimport loggingroot = logging.getLogger()root.setLevel(logging.INFO)log_format = '%(asctime)s %(filename)s: %(message)s'logging.basicConfig(filename="test.log", format=log_format)# incident happenserror_message = 'authentication failed'root.error(f'error: {error_message}')

L’exemple écrit des données personnalisées dans le message du journal.

2019-03-21 14:17:23,196 log_variable.py: error: authentication failed

Voici le message du journal.

Format de journalisation Python datetime

La datetime est incluse dans le message de journal avec l’enregistrement asctimelog. Avec l’option de configuration datefmt, nous pouvons formater la chaîne de temps de la date.

date_time.py
#!/usr/bin/env pythonimport logginglogger = logging.getLogger()logger.setLevel(logging.DEBUG)log_format = '%(asctime)s %(filename)s: %(message)s'logging.basicConfig(filename="test.log", format=log_format, datefmt='%Y-%m-%d %H:%M:%S')logger.info("information message")

L’exemple formate l’heure de la date du message du journal.

log_format = '%(asctime)s %(filename)s: %(message)s'

Nous incluons la chaîne de l’heure de la date dans le journal avec asctime.

logging.basicConfig(filename="test.log", format=log_format, datefmt='%Y-%m-%d %H:%M:%S')

L’option datefmt formate la chaîne de temps de date.

2019-03-21 14:17:23,196 log_variable.py: error: authentication failed2019-03-21 14:23:33 date_time.py: information message

Notez la différence dans le format de la chaîne de temps de date.

La trace de la pile de journalisation de Python

La trace de la pile est une pile d’appels des fonctions qui ont été exécutées jusqu’au moment où une exception a été levée. La trace de la pile estincluse avec l’option exc_info.

stack_trace.py
#!/usr/bin/env pythonimport logginglog_format = '%(asctime)s %(filename)s: %(message)s'logging.basicConfig(filename="test.log", format=log_format)vals = try: print(vals)except Exception as e: logging.error("exception occurred", exc_info=True)

Dans l’exemple, nous enregistrons l’exception qui est lancée lorsque wetry accède à un index de liste inexistant.

logging.error("exception occurred", exc_info=True)

La trace de pile est incluse dans le journal en définissant la exc_infoTrue.

2019-03-21 14:56:21,313 stack_trace.py: exception occurredTraceback (most recent call last): File "C:\Users\Jano\Documents\pyprogs\pylog\stack_trace.py", line 11, in <module> print(vals)IndexError: list index out of range

La trace de pile est incluse dans le journal.

Python logging getLogger

La getLogger() renvoie un logger avec le nom spécifié.Si aucun nom n’est spécifié, elle renvoie le logger racine. C’est une pratique courante de mettre le nom du module à cet endroit avec __name__.

Tous les appels à cette fonction avec un nom donné renvoient la même instance de logger.Cela signifie que les instances de logger n’ont jamais besoin d’être passées entre différentes parties d’une application.

get_logger.py
#!/usr/bin/env pythonimport loggingimport sysmain = logging.getLogger('main')main.setLevel(logging.DEBUG)handler = logging.FileHandler('my.log')format = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')handler.setFormatter(format)main.addHandler(handler)main.info('info message')main.critical('critical message')main.debug('debug message')main.warning('warning message')main.error('error message')

L’exemple crée un nouveau logger avec getLogger().On lui donne un gestionnaire de fichiers et un formateur.

main = logging.getLogger('main')main.setLevel(logging.DEBUG)

Un logger nommé main est créé ; nous définissons le niveau de journalisation à DEBUG.

handler = logging.FileHandler('my.log')

Un gestionnaire de fichier est créé. Les messages seront écrits dans lemy.log fichier.

format = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')handler.setFormatter(format)

Un formateur est créé. Il comprend l’heure, le nom du logger,le niveau de journalisation, et le message dans to log. Le formateur est ajouté au handler avec setFormatter().

main.addHandler(handler)

Le handler est ajouté au logger avec addHandler().

$ cat my.log2019-03-21 14:15:45,439 main INFO: info message2019-03-21 14:15:45,439 main CRITICAL: critical message2019-03-21 14:15:45,439 main DEBUG: debug message2019-03-21 14:15:45,439 main WARNING: warning message2019-03-21 14:15:45,439 main ERROR: error message

Voici les messages de log écrits.

Python logging YAML configuration

Les détails du logging peuvent être définis dans un fichier de configuration YAML.YAML est un langage de sérialisation de données lisible par l’homme. Il est couramment utilisé pour les fichiers de configuration.

$ pip install pyyaml

Nous devons installer le module pyyaml.

config.yaml
version: 1formatters: simple: format: "%(asctime)s %(name)s: %(message)s" extended: format: "%(asctime)s %(name)s %(levelname)s: %(message)s"handlers: console: class: logging.StreamHandler level: INFO formatter: simple file_handler: class: logging.FileHandler level: INFO filename: test.log formatter: extended propagate: falseloggers: dev: handlers: test: handlers: root: handlers: 

Dans le fichier de configuration, nous avons défini divers formateurs, gestionnaires, etloggers. L’option propagate empêche de propager les messages dulog aux loggers parents ; dans notre cas, au logger racine.Sinon, les messages seraient dupliqués.

log_yaml.py
#!/usr/bin/env pythonimport loggingimport logging.configimport yamlwith open('config.yaml', 'r') as f: log_cfg = yaml.safe_load(f.read())logging.config.dictConfig(log_cfg)logger = logging.getLogger('dev')logger.setLevel(logging.INFO)logger.info('This is an info message')logger.error('This is an error message')

Dans l’exemple, nous lisons le fichier de configuration et utilisons le dev logger.

$ log_yaml.py2019-03-28 11:36:54,854 dev: This is an info message2019-03-28 11:36:54,855 dev: This is an error message

Lorsque nous exécutons le programme, il y a deux messages sur la console.Les gestionnaires de la console utilisent le formateur simple avec moins d’informations.

...2019-03-28 11:36:54,854 dev INFO: This is an info message2019-03-28 11:36:54,855 dev ERROR: This is an error message

Il y a des messages de journal à l’intérieur du fichier test.log.Ils sont produits par le formateur étendu avec plus d’informations.

Dans ce tutoriel, nous avons travaillé avec la bibliothèque de journalisation Python.

Liste des tutoriels Python.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *