logger

Comment créer un logger structuré et efficace dans un projet Python

stat4decision Mis à jour le : 13 mars 2025 méthode Leave a Comment

Dans un projet python, la gestion des logs est essentielle pour assurer le suivi, le débogage et la maintenabilité du code, surtout dans les projets data. Un bon logger doit être centralisé, réutilisable, structurant et capable d’écrire dans des fichiers. Dans cet article, nous vous montrons comment mettre en place un logger adapté à un projet Python organisé en plusieurs fichiers, avec des logs enrichis et adaptés aux usages data science. Python comporte un module interne nommé logging qui permet de gérer les logs python.

Objectifs

  • Centraliser les logs de manière propre avec un logger python
  • Écrire les logs dans des fichiers avec rotation
  • Structurer les messages (texte enrichi ou JSON)
  • Journaliser des objets complexes comme des DataFrame (extraits, dimensions)
  • Fournir un outil de monitoring fiable pour les projets data et IA

Structure recommandée du projet

mon_projet/
├── main.py
├── core/
│   ├── traitement.py
│   └── utils.py
├── config/
│   └── logger.py
├── logs/
    └── (fichiers de logs générés ici)

Configuration centrale dans logger.py

Le fichier logger.py contient toute la logique de configuration du logger. Il est conçu pour être appelé depuis n’importe quel module de l’application sans multiplier les handlers (ce qui provoquerait des doublons de logs).

Voici les principaux éléments paramétrables :

  • Nom du logger : souvent __name__, il permet d’identifier le module appelant.
  • Niveau de log : DEBUG, INFO, WARNING, ERROR, CRITICAL selon le degré de verbiage souhaité. On utilise souvent DEBUG en développement, INFO en production.
  • Format du message : avec logging.Formatter, vous pouvez inclure l’heure, le niveau, le module, la ligne et le message.
  • Handler console : pour afficher les logs en temps réel dans le terminal.
  • Handler fichier : ici on utilise un RotatingFileHandler pour limiter la taille des fichiers de logs et conserver un historique.
# config/logger.py

import logging
from logging.handlers import RotatingFileHandler
import os

LOG_DIR = os.path.join(os.path.dirname(__file__), '..', 'logs')
os.makedirs(LOG_DIR, exist_ok=True)

def get_logger(name: str) -> logging.Logger:
    logger = logging.getLogger(name)

    if not logger.hasHandlers():
        logger.setLevel(logging.DEBUG)  # Niveau par défaut : DEBUG

        formatter = logging.Formatter(
            '[%(asctime)s] %(levelname)s | %(name)s | %(filename)s:%(lineno)d | %(message)s'
        )

        # Handler pour la console
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)

        # Handler pour le fichier avec rotation
        file_handler = RotatingFileHandler(
            os.path.join(LOG_DIR, 'application.log'),
            maxBytes=5 * 1024 * 1024,  # 5 Mo
            backupCount=3              # Jusqu'à 3 fichiers de sauvegarde
        )
        file_handler.setFormatter(formatter)

        # Ajout des handlers
        logger.addHandler(console_handler)
        logger.addHandler(file_handler)

    return logger

Vous pouvez adapter ces paramètres selon l’environnement (dev, test, prod) ou les charger depuis un fichier de configuration YAML ou JSON.

Exemple d’utilisation dans les fichiers Python

# core/utils.py

from config.logger import get_logger
logger = get_logger(__name__)

def addition(a, b):
    logger.debug(f"Addition de {a} et {b}")
    return a + b
# core/traitement.py

from config.logger import get_logger
logger = get_logger(__name__)

def process(data):
    logger.info(f"Traitement des données : {data}")

Écriture dans un fichier avec rotation

Les logs sont écrits dans le fichier suivant :

logs/application.log

Les fichiers sont automatiquement archivés si leur taille dépasse 5 Mo, avec jusqu’à 3 backups.

Logs enrichis avec contexte métier

Dans de nombreux projets, notamment en data science ou en business intelligence, il est utile de logguer non seulement des messages techniques, mais aussi du contexte métier pour pouvoir tracer plus facilement l’origine des traitements ou comprendre un comportement sans avoir à lire le code.

Par exemple, on peut vouloir savoir :

  • Quel utilisateur ou quel service a lancé le traitement
  • Sur quelle table ou source de données il a été appliqué
  • Dans quel environnement cela s’est produit (dev, staging, prod)
  • Quelle est l’ID du batch ou du job concerné

Python permet d’ajouter des champs personnalisés via le paramètre extra de la fonction logger.info() (ou tout autre niveau). Voici un exemple :

logger.info("Import terminé avec succès", extra={
    'user': 'analyste1',
    'table': 'ventes',
    'env': 'production',
    'job_id': 'batch_20250310'
})

Pour que ces informations apparaissent dans les logs, il faut adapter le formatter à ces clés supplémentaires. Exemple :

formatter = logging.Formatter(
    '[%(asctime)s] %(levelname)s | %(name)s | %(message)s | user=%(user)s table=%(table)s env=%(env)s job_id=%(job_id)s'
)

Cette approche est particulièrement utile pour les logs structurés en JSON, utilisables ensuite dans des outils comme ELK, Splunk ou Datadog.

Format JSON (optionnel)

Pour une intégration avec des outils comme ELK ou Datadog, vous pouvez formater vos logs en JSON. On devra utiliser un package supplémentaire, python-json-logger, pour l’installer, il vous suffit de lancer cette commande dans votre terminal :

pip install python-json-logger

Ensuite dans votre code python :

from pythonjsonlogger import jsonlogger

json_formatter = jsonlogger.JsonFormatter('%(asctime)s %(levelname)s %(name)s %(message)s %(user)s')
file_handler.setFormatter(json_formatter)

Journaliser un DataFrame Pandas

Il est inutile (et risqué) de logger un DataFrame complet. En revanche, vous pouvez logguer :

  • Le nombre de lignes et de colonnes
  • Les noms et types de colonnes
  • Un petit aperçu du contenu
import pandas as pd
from config.logger import get_logger

logger = get_logger(__name__)

def analyser_dataframe(df: pd.DataFrame):
    logger.info(f"DataFrame chargé : {df.shape[0]} lignes × {df.shape[1]} colonnes")
    logger.debug(f"Colonnes : {list(df.columns)}")
    logger.debug(f"Types : {df.dtypes.to_dict()}")
    logger.debug(f"Aperçu :\n{df.head(3).to_string(index=False)}")

Bonnes pratiques

  • Utiliser un fichier logger.py unique pour toute l’application
  • Éviter les doublons de handlers avec if not logger.hasHandlers()
  • Enrichir les logs avec du contexte utile (utilisateur, table, environnement)
  • Ne pas loguer de données sensibles
  • En production, adapter les niveaux et les formats (JSON + INFO/ERROR)
  • Externaliser la configuration dans un fichier si nécessaire pour la rendre modulaire

Conclusion

Un bon logger vous évitera bien des heures de débogage et facilitera l’analyse de vos processus data. Centralisez-le, structurez-le, et adaptez-le à votre contexte.

Les fichiers liés à ce blog sont disponible dans ce répertoire GitHub.

Chez stat4decision, nous l’intégrons systématiquement dans nos projets et nos formations pour garantir transparence et traçabilité. Découvrez nos accompagnements python.

Partager cet article

Laisser un commentaire

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

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.