Comment utiliser la journalisation en Python 3

introduction

Le modulelogging fait partie de la bibliothèque Python standard et fournit un suivi des événements qui se produisent pendant l'exécution du logiciel. Vous pouvez ajouter des appels de journalisation à votre code pour indiquer les événements survenus.

Le modulelogging permet à la fois la journalisation des diagnostics qui enregistre les événements liés à l'opération d'une application, ainsi que la journalisation d'audit qui enregistre les événements des transactions d'un utilisateur à des fins d'analyse. Il est particulièrement utilisé pour enregistrer des événements dans un fichier.

Pourquoi utiliser le modulelogging

Le modulelogging conserve un enregistrement des événements qui se produisent dans un programme, ce qui permet de voir la sortie liée à l'un des événements qui se produisent tout au long de l'exécution d'un logiciel.

Vous serez peut-être plus habitué à vérifier que des événements se produisent en utilisant l'instructionprint() dans tout votre code. L'instructionprint()does fournit un moyen de base pour déboguer votre code pour résoudre les problèmes. Bien que l'incorporation d'instructionsprint() dans votre code puisse suivre le flux d'exécution et l'état actuel de votre programme, cette solution s'avère moins maintenable que l'utilisation du modulelogging pour plusieurs raisons:

  • Il devient difficile de faire la distinction entre une sortie de débogage et une sortie de programme normale car les deux sont mélangés

  • Lors de l'utilisation d'instructionsprint() dispersées dans le code, il n'y a pas de moyen simple de désactiver celles qui fournissent une sortie de débogage

  • Il devient difficile de supprimer toutes les instructionsprint() lorsque vous avez terminé le débogage

  • Il n'y a pas d'enregistrement de journal contenant des informations de diagnostic facilement disponibles.

C'est une bonne idée de prendre l'habitude d'utiliser le modulelogging dans votre code car il est plus adapté aux applications qui se développent au-delà des simples scripts Python et fournit une approche durable du débogage.

Comme les journaux peuvent indiquer votre comportement et vos erreurs au fil du temps, ils peuvent également vous donner une meilleure vue d'ensemble de ce qui se passe dans votre processus de développement d'applications.

Impression de messages de débogage sur la console

Si vous avez l'habitude d'utiliser l'instructionprint() pour voir ce qui se passe dans un programme, vous pouvez être habitué à voir un programme quidefines a class et instancie des objets qui ressemblent à ceci:

pizza.py

class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        print("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        print("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        print("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

Le code ci-dessus a une méthode__init__ pour définir lesname etprice d'un objet de la classePizza. Il dispose alors de deux méthodes, l'une appeléemake() pour faire des pizzas et l'autre appeléeeat() pour manger des pizzas. Ces deux méthodes prennent le paramètre dequantity, qui est initialisé à1.

Lançons maintenant le programme:

python pizza.py

Nous recevrons le résultat suivant:

OutputPizza created: artichoke ($15)
Made 1 artichoke pizza(s)
Ate 1 pizza(s)
Pizza created: margherita ($12)
Made 2 margherita pizza(s)
Ate 1 pizza(s)

Alors que l'instructionprint() nous permet de voir que le code fonctionne, nous pouvons utiliser le modulelogging pour le faire à la place.

Supprimons ou commentons les instructionsprint() dans tout le code, et ajoutonsimport logging en haut du fichier:

pizza.py

import logging


class Pizza():
    def __init__(self, name, value):
        self.name = name
        self.value = value
...

Le modulelogging a undefault level deWARNING, qui est un niveau supérieur àDEBUG. Puisque nous allons utiliser le modulelogging pour le débogage dans cet exemple, nous devons modifier la configuration afin que le niveau delogging.DEBUG renvoie les informations à la console pour nous. Nous pouvons le faire en ajoutant la ligne suivante sous lesimport statement:

pizza.py

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

Ce niveau delogging.DEBUG fait référence à une valeur entière constante que nous référencons dans le code ci-dessus pour définir un seuil. Le niveau deDEBUG est de 10.

Maintenant, nous allons remplacer toutes les instructionsprint() par des instructionslogging.debug() à la place. Contrairement àlogging.DEBUG qui est une constante,logging.debug() est une méthode du modulelogging. Lorsque vous travaillez avec cette méthode, nous pouvons utiliser les mêmesstring passés àprint(), comme indiqué ci-dessous.

pizza.py

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

À ce stade, lorsque nous exécutons le programme avec la commandepython pizza.py, nous recevrons cette sortie:

OutputDEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

Les messages du journal ont le niveau de gravitéDEBUG ainsi que le motroot intégré, qui fait référence au niveau de votre module Python. Le modulelogging peut être utilisé avec une hiérarchie d'enregistreurs qui ont des noms différents, de sorte que vous pouvez utiliser un enregistreur différent pour chacun de vos modules.

Par exemple, vous pouvez définir des enregistreurs égaux à différents enregistreurs ayant des noms et des sorties différents:

logger1 = logging.getLogger("module_1")
logger2 = logging.getLogger("module_2")

logger1.debug("Module 1 debugger")
logger2.debug("Module 2 debugger")
OutputDEBUG:module_1:Module 1 debugger
DEBUG:module_2:Module 2 debugger

Maintenant que nous avons compris comment utiliser le modulelogging pour imprimer des messages sur la console, passons à l'utilisation du modulelogging pour imprimer les messages dans un fichier.

Journalisation des messages dans un fichier

L'objectif principal du modulelogging est de consigner les messages dans un fichier plutôt que dans une console. Conserver un fichier de messages vous fournit des données au fil du temps que vous pouvez consulter et quantifier afin que vous puissiez voir quelles modifications doivent être apportées à votre code.

Pour commencer la journalisation dans un fichier, nous pouvons modifier la méthodelogging.basicConfig() pour inclure un paramètrefilename. Dans ce cas, appelons le nom de fichiertest.log:

pizza.py

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

Le code ci-dessus est identique à celui de la section précédente, sauf que nous avons maintenant ajouté le nom de fichier du journal dans lequel imprimer. Une fois que nous avons exécuté le code avec la commandepython pizza.py, nous devrions avoir un nouveau fichier dans notre répertoire appelétest.log.

Ouvrons le fichiertest.log avec nano (ou l’éditeur de texte de votre choix):

nano test.log

Lorsque le fichier sera ouvert, nous verrons ce qui suit:

test.log

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

Ceci est similaire à la sortie de la console que nous avons rencontrée dans la section précédente, sauf qu'elle se trouve maintenant dans le fichiertest.log.

Fermons le fichier avecCTRL +x et retournons dans le fichierpizza.py afin que nous puissions modifier le code.

Nous conserverons une grande partie du code, mais modifierons les paramètres dans les deux instances de pizza,pizza_01 etpizza_02:

pizza.py

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

# Modify the parameters of the pizza_01 object
pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

# Modify the parameters of the pizza_02 object
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

Avec ces modifications, exécutons à nouveau le programme avec la commandepython pizza.py.

Une fois le programme lancé, nous pouvons ouvrir à nouveau notre fichiertest.log avec nano:

nano test.log

Lorsque nous examinons le fichier, nous constatons que plusieurs nouvelles lignes ont été ajoutées et que les lignes précédentes de la dernière exécution du programme ont été conservées:

test.log

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)

Bien que ces informations soient certainement utiles, nous pouvons rendre le journal plus informatif en ajoutant desLogRecord attributes supplémentaires. En premier lieu, nous souhaiterions ajouter un horodatage lisible par l’homme nous indiquant la date de création de LogRecord.

Nous pouvons ajouter cet attribut à un paramètre appeléformat, en le référençant comme indiqué dans le tableau avec la chaîne%(asctime)s. De plus, pour conserver le nom du niveauDEBUG, nous devrons inclure la chaîne%(levelname)s et pour conserver la chaîne de message que nous demandons à l'enregistreur d'imprimer, nous inclurons%(message)s. Chacun de ces attributs sera séparé par uncolon, comme indiqué dans le code ajouté ci-dessous.

pizza.py

import logging

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


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

Lorsque nous exécutons le code ci-dessus avec les attributs ajoutés avec la commandepython pizza.py, nous obtenons de nouvelles lignes ajoutées à notre fichiertest.log qui incluent l'horodatage lisible par l'homme en plus du nom de niveau deDEBUG et les messages associés qui sont transmis à l'enregistreur sous forme de chaînes.

OutputDEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)
2017-05-01 16:28:54,593:DEBUG:Pizza created: Sicilian ($18)
2017-05-01 16:28:54,593:DEBUG:Made 5 Sicilian pizza(s)
2017-05-01 16:28:54,593:DEBUG:Ate 4 pizza(s)
2017-05-01 16:28:54,593:DEBUG:Pizza created: quattro formaggi ($16)
2017-05-01 16:28:54,593:DEBUG:Made 2 quattro formaggi pizza(s)
2017-05-01 16:28:54,593:DEBUG:Ate 2 pizza(s)

En fonction de vos besoins, vous souhaiterez peut-être utiliser desLogRecord attributes supplémentaires dans votre code afin de rendre les journaux de vos fichiers programme pertinents pour vous.

La journalisation du débogage et d’autres messages dans des fichiers séparés vous offre une compréhension globale de votre programme Python au fil du temps, ce qui vous permet de dépanner et de modifier votre code de manière à tenir compte des travaux événements et transactions qui se produisent.

Tableau des niveaux de journalisation

En tant que développeur, vous pouvez attribuer un niveau d'importance à l'événement capturé dans le consignateur en ajoutant un niveau de gravité. Les niveaux de gravité sont indiqués dans le tableau ci-dessous.

Les niveaux d'enregistrement sont techniquement des entiers (une constante), et ils sont tous par incréments de 10, en commençant parNOTSET qui initialise l'enregistreur à la valeur numérique de 0.

Vous pouvez également définir vos propres niveaux par rapport aux niveaux prédéfinis. Si vous définissez un niveau avec la même valeur numérique, vous écraserez le nom associé à cette valeur.

Le tableau ci-dessous présente les différents noms de niveau, leur valeur numérique, la fonction que vous pouvez utiliser pour appeler le niveau et l’utilisation de ce niveau.

Niveau Valeur numérique Une fonction Habitué

CRITICAL

50

logging.critical()

Afficher une erreur grave, le programme peut être incapable de continuer à s'exécuter

ERROR

40

logging.error()

Montrer un problème plus grave

WARNING

30

logging.warning()

Indiquez que quelque chose d'inattendu s'est produit ou pourrait se produire

INFO

20

logging.info()

Confirmez que les choses fonctionnent comme prévu

DEBUG

10

logging.debug()

Diagnostiquer les problèmes, afficher des informations détaillées

Le modulelogging définit le niveau par défaut àWARNING, doncWARNING,ERROR etCRITICAL seront tous consignés par défaut. Dans l'exemple ci-dessus, nous avons modifié la configuration pour inclure le niveauDEBUG avec le code suivant:

logging.basicConfig(level=logging.DEBUG)

Vous pouvez en savoir plus sur les commandes et l'utilisation du débogueur à partir desofficial logging documentation.

Conclusion

Le débogage est une étape importante de tout projet de développement logiciel. Le modulelogging fait partie de la bibliothèque Python standard, fournit un suivi des événements qui se produisent pendant l'exécution du logiciel et peut générer ces événements dans un fichier journal séparé pour vous permettre de suivre ce qui se passe pendant l'exécution de votre code. Cela vous donne la possibilité de déboguer votre code en fonction de la compréhension des divers événements qui surviennent lors de l'exécution de votre programme.