Présentation du module de journalisation

Beginner

This tutorial is from open-source community. Access the source code

Introduction

Cette section présente brièvement le module de journalisation.

Module de journalisation

Le module logging est un module de la bibliothèque standard pour enregistrer des informations de diagnostic. C'est également un module très vaste avec beaucoup de fonctionnalités sophistiquées. Nous allons montrer un exemple simple pour illustrer son utilité.

Exceptions Revisitées

Dans les exercices, nous avons écrit une fonction parse() qui ressemblait à ceci :

## fileparse.py
def parse(f, types=None, names=None, delimiter=None):
    records = []
    for line in f:
        line = line.strip()
        if not line: continue
        try:
            records.append(split(line,types,names,delimiter))
        except ValueError as e:
            print("Couldn't parse :", line)
            print("Reason :", e)
    return records

Prenons en compte l'énoncé try-except. Que devriez-vous faire dans le bloc except?

Devriez-vous afficher un message d'avertissement?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    print("Couldn't parse :", line)
    print("Reason :", e)

Ou l'ignorez-vous silencieusement?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    pass

Aucune des solutions n'est satisfaisante car vous voulez souvent les deux comportements (sélectionnables par l'utilisateur).

Utilisation de la journalisation

Le module logging peut résoudre ce problème.

## fileparse.py
import logging
log = logging.getLogger(__name__)

def parse(f,types=None,names=None,delimiter=None):
  ...
    try:
        records.append(split(line,types,names,delimiter))
    except ValueError as e:
        log.warning("Couldn't parse : %s", line)
        log.debug("Reason : %s", e)

Le code est modifié pour émettre des messages d'avertissement ou d'un objet Logger spécial. Celui créé avec logging.getLogger(__name__).

Les bases de la journalisation

Créer un objet journal.

log = logging.getLogger(name)   ## name est une chaîne de caractères

Émettre des messages de journal.

log.critical(message [, args])
log.error(message [, args])
log.warning(message [, args])
log.info(message [, args])
log.debug(message [, args])

Chacune des méthodes représente un niveau différent de gravité.

Toutes créent un message de journal formaté. args est utilisé avec l'opérateur % pour créer le message.

logmsg = message % args ## Écrit dans le journal

Configuration de la journalisation

Le comportement de la journalisation est configuré séparément.

## main.py

...

if __name__ == '__main__':
    import logging
    logging.basicConfig(
        filename  = 'app.log',      ## Fichier de sortie du journal
        level     = logging.INFO,   ## Niveau de sortie
    )

En général, il s'agit d'une configuration unique au démarrage du programme. La configuration est séparée du code qui effectue les appels de journalisation.

Commentaires

La journalisation est hautement configurable. Vous pouvez ajuster tous les aspects de celle-ci : fichiers de sortie, niveaux, formats de message, etc. Cependant, le code qui utilise la journalisation n'a pas à s'en préoccuper.

Exercice 8.2 : Ajout de la journalisation à un module

Dans fileparse.py, il y a une gestion d'erreurs liée aux exceptions causées par des entrées invalides. Elle ressemble à ceci :

## fileparse.py
import csv

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Analyser un fichier CSV en une liste d'enregistrements avec conversion de type.
    '''
    if select and not has_headers:
        raise RuntimeError('select nécessite des en-têtes de colonne')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Lire les en-têtes du fichier (s'il y en a)
    headers = next(rows) if has_headers else []

    ## Si des colonnes spécifiques ont été sélectionnées, créer des indices pour le filtrage et définir les colonnes de sortie
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Ignorer les lignes sans données
            continue

        ## Si des indices de colonne spécifiques sont sélectionnés, les extraire
        if select:
            row = [ row[index] for index in indices]

        ## Appliquer la conversion de type à la ligne
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    print(f"Ligne {rowno}: Impossible de convertir {row}")
                    print(f"Ligne {rowno}: Raison {e}")
                continue

        ## Créer un dictionnaire ou un tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

Remarquez les instructions print qui émettent des messages de diagnostic. Remplacer ces print par des opérations de journalisation est relativement simple. Modifiez le code comme ceci :

## fileparse.py
import csv
import logging
log = logging.getLogger(__name__)

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Analyser un fichier CSV en une liste d'enregistrements avec conversion de type.
    '''
    if select and not has_headers:
        raise RuntimeError('select nécessite des en-têtes de colonne')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Lire les en-têtes du fichier (s'il y en a)
    headers = next(rows) if has_headers else []

    ## Si des colonnes spécifiques ont été sélectionnées, créer des indices pour le filtrage et définir les colonnes de sortie
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Ignorer les lignes sans données
            continue

        ## Si des indices de colonne spécifiques sont sélectionnés, les extraire
        if select:
            row = [ row[index] for index in indices]

        ## Appliquer la conversion de type à la ligne
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    log.warning("Ligne %d: Impossible de convertir %s", rowno, row)
                    log.debug("Ligne %d: Raison %s", rowno, e)
                continue

        ## Créer un dictionnaire ou un tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

Maintenant que vous avez effectué ces modifications, essayez d'utiliser un peu de votre code avec des données invalides.

>>> import report
>>> a = report.read_portfolio('missing.csv')
Ligne 4: Mauvaise ligne : ['MSFT', '', '51.23']
Ligne 7: Mauvaise ligne : ['IBM', '', '70.44']
>>>

Si vous ne faites rien, vous ne recevrez que les messages de journalisation pour le niveau WARNING et supérieur. La sortie ressemblera à des instructions print simples. Cependant, si vous configurez le module de journalisation, vous recevrez des informations supplémentaires sur les niveaux de journalisation, le module, etc. Tapez ces étapes pour voir cela :

>>> import logging
>>> logging.basicConfig()
>>> a = report.read_portfolio('missing.csv')
AVERTISSEMENT:fileparse:Ligne 4: Mauvaise ligne : ['MSFT', '', '51.23']
AVERTISSEMENT:fileparse:Ligne 7: Mauvaise ligne : ['IBM', '', '70.44']
>>>

Vous remarquerez que vous ne voyez pas la sortie de l'opération log.debug(). Tapez ceci pour changer le niveau.

>>> logging.getLogger('fileparse').setLevel(logging.DEBUG)
>>> a = report.read_portfolio('missing.csv')
AVERTISSEMENT:fileparse:Ligne 4: Mauvaise ligne : ['MSFT', '', '51.23']
DEBUG:fileparse:Ligne 4: Raison : valeur littérale invalide pour int() avec base 10 : ''
AVERTISSEMENT:fileparse:Ligne 7: Mauvaise ligne : ['IBM', '', '70.44']
DEBUG:fileparse:Ligne 7: Raison : valeur littérale invalide pour int() avec base 10 : ''
>>>

Désactivez tous les messages de journalisation, sauf les plus critiques :

>>> logging.getLogger('fileparse').setLevel(logging.CRITIQUE)
>>> a = report.read_portfolio('missing.csv')
>>>

Exercice 8.3 : Ajout de la journalisation à un programme

Pour ajouter la journalisation à une application, vous avez besoin d'un mécanisme pour initialiser le module de journalisation dans le module principal. Une manière de faire cela consiste à inclure du code de configuration qui ressemble à ceci :

## Ce fichier configure de base le module de journalisation.
## Changez les paramètres ici pour ajuster la sortie de journalisation selon vos besoins.
import logging
logging.basicConfig(
    filename = 'app.log',            ## Nom du fichier de journal (omettez pour utiliser stderr)
    filemode = 'w',                  ## Mode d'ouverture du fichier (utilisez 'a' pour ajouter)
    level    = logging.WARNING,      ## Niveau de journalisation (DEBUG, INFO, WARNING, ERROR ou CRITICAL)
)

Encore une fois, vous devriez placer ce code quelque part dans les étapes de démarrage de votre programme. Par exemple, où placeriez-vous ce code dans votre programme report.py?

Sommaire

Félicitations ! Vous avez terminé le laboratoire sur la journalisation. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.