Question 1Avec logging.basicConfig(level=logging.INFO), quel niveau est supprimé ?
logging — Enregistrer au lieu de print
Les 5 niveaux de log et basicConfig, les codes de format comme %(levelname)s, et la sortie fichier via FileHandler pour abandonner le débogage à coups de print.
logging est le module standard pour enregistrer le comportement de ton app par étapes. Écrire des sorties de débogage avec print rencontre des problèmes : tu oublies de les retirer en production, tu ne peux pas dire ce qui est important, et passer à une sortie fichier plus tard signifie tout réécrire. Avec logging, tu obtiens des niveaux d'importance (DEBUG / INFO / WARNING / ERROR / CRITICAL) pour filtrer la sortie, Formatter pour un formatage unifié, et Handler pour basculer les destinations (stdout / fichier / distant).
logger.info(...) depuis ton code, le flux est Logger filtre par niveau → Handler décide de la destination → Formatter assemble le format. Comme les trois responsabilités sont séparées, changer le format ou la destination à un seul endroit change tout le comportement.Niveaux de log — 5 paliers d'importance
logging a 5 niveaux, et seuls les messages au niveau fixé sur le Logger ou au-dessus sont émis. La pratique standard est DEBUG pendant le développement et INFO ou WARNING en production — et pouvoir changer le volume de sortie depuis un seul réglage sans toucher au code est la plus grande différence avec print.
Choisir entre WARNING / ERROR / DEBUG
Avec le logger configuré, appelle des niveaux autres que INFO et observe la sortie. Comme le niveau est fixé à INFO, DEBUG ne produit aucune sortie du tout — confirme aussi ça. C'est la fondation du comportement "changer le volume de sortie depuis un réglage" de logging.
Émettre des logs vers un fichier
Les projets réels ont presque toujours besoin que les logs persistent dans un fichier, pas seulement à l'écran. Plus tard tu voudras grep pour une investigation post-mortem ou les ingérer dans un outil de monitoring. Passe filename="app.log" à basicConfig et la destination du root logger bascule vers un fichier — faire la même chose avec print signifierait réécrire chaque appel print, mais avec logging tu changes une ligne de config pour rediriger.
logging, tu bascules entre les deux en une ligne de config.filemode est soit "a" (par défaut, append) soit "w" (overwrite). Append est le choix de production — garde l'historique. Overwrite est souvent plus pratique pendant le développement — repart à zéro à chaque fois. Pour émettre à la fois à l'écran et au fichier, tu sautes basicConfig et combines explicitement StreamHandler + FileHandler (couvert plus loin).
StreamHandler et FileHandler séparément et addHandler pour chacun.import logging
import os
# Crée d'abord le dossier parent (FileHandler ne le crée pas automatiquement)
os.makedirs("logs", exist_ok=True)
# Passe filename à basicConfig pour basculer la destination vers un fichier
logging.basicConfig(
level=logging.INFO,
format="[%(levelname)s] %(message)s",
filename="logs/app.log", # Sortie fichier
filemode="w", # "a" (append) ou "w" (overwrite)
force=True,
)
logger = logging.getLogger("app")
logger.info("Démarré")
logger.warning("Config obsolète")
logger.error("Échec de connexion à la BD")
# Relit le fichier pour confirmer le contenu
with open("logs/app.log") as f:
print(f.read(), end="")
Définir les handlers dans un fichier séparé
À mesure que ton app grandit, extrais la config du logger (format, niveau, destination) dans un module dédié et fais que chaque module l'import. Les configs de logger ne se dupliquent plus, et tu changes le format/destination à un seul endroit. Le motif : construire un StreamHandler directement, attacher un Formatter, enregistrer avec logger.addHandler(...) — tout ça vit dans un fichier séparé.
main.py / orders.py / users.py) appelle juste setup_logger(__name__) pour obtenir un logger configuré. Pour changer le format ou la destination, édite seulement log_setup.py.# log_setup.py — module dédié à la config du logger
import logging
def setup_logger(name):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
if not logger.handlers: # Empêche l'enregistrement en double
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter("[%(name)s] [%(levelname)s] %(message)s")
)
logger.addHandler(handler)
logger.propagate = False # Ne propage pas au root
return logger
# main.py — importe log_setup et l'utilise
from log_setup import setup_logger
logger = setup_logger("orders")
logger.info("Commande reçue")
Format plus détaillé — horodatage / module / ligne
La chaîne de format de Formatter accepte des codes de remplacement style %(...)s, te permettant d'empaqueter les infos opérationnellement pertinentes sur une seule ligne. Voici les six codes les plus utilisés avec exemples. Les noms de logger peuvent être hiérarchiques via la forme séparée par points comme parent.child.grandchild — appeler getLogger(__name__) depuis chaque module enregistre automatiquement "quel module a émis ce log".
| Code | Ce qu'il sort | Exemple |
|---|---|---|
| %(asctime)s | Horodatage | 2024-12-01 10:30:45 |
| %(name)s | Nom du logger (séparé par points pour la hiérarchie) | app.orders |
| %(levelname)s | Niveau du log | INFO / WARNING / ERROR |
| %(funcName)s | Nom de la fonction appelante | process_order |
| %(lineno)d | Numéro de ligne appelant | 42 |
| %(message)s | Corps du message | Traitement de la commande |
import logging
# Format détaillé pour la production
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] [%(levelname)s] %(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
force=True,
)
logger = logging.getLogger("app.orders")
def process_order(order_id):
logger.info(f"Traitement de la commande {order_id}")
process_order(1234)
# Exemple de sortie :
# 2024-12-01 10:30:45 [app.orders] [INFO] process_order:13 - Traitement de la commande 1234
Extraire la config dans un fichier yaml
Une fois que la configuration du logger devient complexe, au lieu de coder en dur format / handler / niveau dans Python, extrais-les dans un fichier de config yaml. logging.config.dictConfig(...) accepte une config en dict, donc parse simplement le yaml et passe-le pour configurer toute l'installation du logger. Ta base de code gère soudainement "des fichiers yaml différents pour production / staging / développement" sans changements.
# logging.yml — single source for logger config
version: 1
disable_existing_loggers: false
formatters:
default:
format: "[%(name)s] [%(levelname)s] %(message)s"
handlers:
console:
class: logging.StreamHandler
formatter: default
level: INFO
loggers:
app:
level: INFO
handlers: [console]
propagate: false
| Clé | Rôle | Notes |
|---|---|---|
| version | Version du schéma dictConfig | Actuellement seulement 1. Requis. |
| disable_existing_loggers | Désactiver ou non les loggers existants | false recommandé (true fait taire les loggers comme app.orders d'avant) |
| formatters | Liste nommée de Formatters | Définis sous n'importe quel nom (ex. default), référencé depuis handlers |
| formatters.<name>.format | Chaîne de format (codes %(...)s) | Identique à l'argument Formatter dans le code |
| handlers | Liste nommée de Handlers (destinations) | Définis console / file / mail et ainsi de suite |
| handlers.<name>.class | Nom de classe Handler complètement qualifié | logging.StreamHandler / logging.FileHandler / logging.handlers.RotatingFileHandler etc. |
| handlers.<name>.formatter | Nom du Formatter à appliquer | Utilise une clé définie sous formatters |
| handlers.<name>.level | Niveau par Handler | Filtre la sortie plus finement qu'au niveau Logger |
| loggers | Liste nommée de Loggers | loggers.app est récupéré via logging.getLogger("app") |
| loggers.<name>.handlers | Liste de noms de Handlers attachés à ce logger | Plusieurs OK, comme [console, file] |
| loggers.<name>.propagate | Propagation aux loggers parents ou non | false arrête la propagation au root logger (évite la double sortie) |
Rotation des logs
Si les logs continuent de croître, le fichier gonfle indéfiniment, donc la production a besoin de rotation — renommer les vieux fichiers en un nom séparé et les supprimer éventuellement. logging.handlers propose deux variantes — basée sur la taille et basée sur le temps — et tu choisis selon le cas d'usage.
RotatingFileHandler — basée sur la taille
RotatingFileHandler(filename, maxBytes, backupCount) est un Handler qui bascule vers un nouveau fichier quand maxBytes serait dépassé. Les vieux fichiers sont renommés avec un suffixe numérique (app.log.1 / app.log.2), et une fois backupCount dépassé le plus ancien est supprimé. Avec maxBytes=10_000_000 (10 Mo) + backupCount=5, tes logs cyclent dans un plafond de 60 Mo.
maxBytes, il est renommé app.log → app.log.1, et un nouveau app.log est créé pour continuer à écrire. Si app.log.N existe déjà, il est décalé en app.log.N → app.log.N+1, et le fichier le plus ancien au-delà de backupCount est supprimé.import logging
from logging.handlers import RotatingFileHandler
# Basée sur la taille : rotation quand maxBytes est dépassé
size_handler = RotatingFileHandler(
"app.log",
maxBytes=10_000_000, # Rotation quand ce serait dépassé (10 Mo)
backupCount=5, # Conserve app.log.1 à app.log.5 (max 60 Mo total)
)
size_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
TimedRotatingFileHandler — basée sur le temps
TimedRotatingFileHandler(filename, when, interval, backupCount) rotate à la frontière temporelle spécifiée par when. Utilise when="midnight" (chaque jour à 0h00), "H" (par heure), "M" (minute), "S" (seconde), "D" (jour), et ainsi de suite. Les backups portent des suffixes d'horodatage comme app.log.2024-12-01_00-00-00, donc le nom de fichier te dit "quand ce log a été écrit" directement.
app.log s'ouvre. Les fichiers horodatés plus anciens au-delà de backupCount sont auto-supprimés.import logging
from logging.handlers import TimedRotatingFileHandler
# Basée sur le temps : rotation chaque jour à minuit
day_handler = TimedRotatingFileHandler(
"app.log",
when="midnight", # "S" sec / "M" min / "H" heure / "D" jour / "midnight" etc.
interval=1, # Tous les N unités (when="H", interval=6 signifie toutes les 6 heures)
backupCount=30, # Conserve 30 jours d'historique
)
day_handler.setFormatter(
logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
)
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Quand tu passes filename="app.log" à logging.basicConfig, où vont les logs ?
Question 3Quel est le bénéfice principal d'extraire la config du logger (format / niveau / handler) dans un module séparé ?