Dépassez le débogage par print() pour un logging structuré et à niveaux que vous pouvez monter ou baisser sans toucher au code — un logger, un handler et un formatter, une sortie console-et-fichier, et les habitudes de débogage qui vont avec : lire une traceback de bas en haut et poser un breakpoint().
Arrêtez de déboguer avec print(). Mettez en place un vrai logging en Python — un logger, un handler et un formatter ; les cinq niveaux (DEBUG, INFO, WARNING, ERROR, CRITICAL) ; journaliser à la fois dans la console et dans un fichier ; et régler la verbosité par niveau sans toucher au code. Plus les compétences de débogage qui vont avec : lire une traceback de bas en haut, et poser un breakpoint() pour inspecter l’état.
Date de publication
18 juillet 2026
Modifié
18 juillet 2026
AstucePoints clés
Le logging bat print() parce qu’il a des niveaux, une source et un interrupteur. Une ligne de log porte un niveau (son importance) et son nom de logger (d’où elle vient), et vous pouvez faire taire toute une catégorie — ou tout un module — en montant un seul niveau, sans supprimer une seule ligne. print() n’a rien de tout cela : c’est tout ou rien, sans étiquette, et vous le supprimez avant de livrer.
Les cinq niveaux forment un cadran : DEBUG · INFO · WARNING · ERROR · CRITICAL. Fixez le seuil et tout ce qui est en dessous se tait. Travaillez en DEBUG pendant le développement, en INFO ou WARNING en production — même code, verbosité différente.
Un logger écrit à travers des handlers, et les handlers mettent en forme avec un formatter. Le logger (obtenu avec logging.getLogger(__name__)) est l’objet que vous appelez ; un handler décide où va un enregistrement (console, fichier, les deux) ; un formatter décide de l’apparence de chaque ligne. Réussissez cette structure en trois parties et vous pouvez journaliser à la fois dans la console et dans un fichier tournant, chacun à son propre niveau.
Lisez une traceback de bas en haut. La dernière ligne est l’erreur réelle (KeyError: 'SUMMER25') ; le cadre juste au-dessus indique où elle s’est produite ; chaque cadre au-dessus est celui qui l’a appelé. Commencez par le bas, pas par le haut.
breakpoint() bat une dispersion de print() pour du vrai débogage. Posez breakpoint() sur la ligne suspecte et Python vous plonge dans un débogueur interactif à cet endroit précis, où vous pouvez inspecter n’importe quelle variable et avancer pas à pas — bien plus que ce qu’un print peut vous dire.
Introduction
Vous avez ajouté un print() pour voir pourquoi le total d’une commande était faux. Puis cinq de plus. Maintenant votre terminal défile avec print(cart), print("here"), print(total) — impossible de savoir quelle ligne a affiché quoi, aucun moyen de distinguer une erreur d’une note de routine, et il faudra tous les retrouver avant de valider. Le débogage par print marche trente secondes et s’effondre dès que le programme dépasse le simple script.
Python fournit un meilleur outil dans sa bibliothèque standard : le module logging. Un log est un message étiqueté — il porte un niveau qui dit son importance et un nom qui dit d’où il vient — et vous décidez, en un seul endroit, quels messages afficher et où les envoyer. Tournez le cadran sur DEBUG pendant que vous traquez un bug ; mettez-le sur WARNING en production pour que seuls les vrais problèmes remontent — sans toucher au code entre les deux.
Cette leçon construit le vrai motif dans l’ordre où ça fait tilt. D’abord votre premier logger — un appel, une sortie que vous voyez, et les cinq niveaux. Puis pourquoi le logging bat print, rendu concret. Puis la partie que tout le monde rate : la structure logger → handler → formatter, et comment elle vous permet d’écrire à la fois dans la console et un fichier. Puis régler la verbosité par niveau. Puis les compétences de débogage qui vont avec : lire une traceback de bas en haut et poser un breakpoint() pour inspecter un programme en cours d’exécution. Tout ce qui peut s’exécuter s’exécute — logging est de la pure bibliothèque standard, donc la cellule Essayez en direct vers la fin s’exécute dans votre navigateur, sans rien à installer.
Votre premier logger
Voici toute l’idée en un seul bloc. Configurez le logging une fois, puis journalisez à chacun des cinq niveaux. Deux détails font apparaître la sortie proprement : logging écrit par défaut sur la sortie d’erreur standard (qu’un notebook ou cette page n’affiche pas), donc nous le pointons vers la sortie standard avec stream=sys.stdout ; et le logger racine n’affiche par défaut que WARNING et au-dessus, donc nous mettons level=logging.DEBUG pour tout voir. force=True efface toute configuration antérieure pour que réexécuter reste propre.
import logging, syslogging.basicConfig( stream=sys.stdout, # show messages here (default is stderr) level=logging.DEBUG, # show every level, DEBUG and upformat="%(levelname)-8s%(message)s", # what each line looks like force=True, # reset any earlier config so re-runs are clean)logging.debug("cache miss for user 42")logging.info("request handled in 12 ms")logging.warning("disk space at 85%")logging.error("payment gateway timed out")logging.critical("database connection lost")
DEBUG cache miss for user 42
INFO request handled in 12 ms
WARNING disk space at 85%
ERROR payment gateway timed out
CRITICAL database connection lost
Cinq lignes en entrée, cinq lignes étiquetées en sortie — chacune marquée de son niveau. Cette étiquette est tout l’intérêt : d’un coup d’œil vous distinguez une note de routine (INFO) d’un vrai problème (ERROR), et plus tard vous ferez taire les bruyantes sans les supprimer. logging.basicConfig est la configuration en un appel — elle configure le logger racine intégré, celui que chaque appel logging.info(...) utilise quand vous n’en demandez pas un précis.
Un niveau de log n’est qu’un classement de gravité. Python en définit cinq, et chacun répond à une question différente :
Niveau
À utiliser pour
Message typique
DEBUG
Diagnostics détaillés pour le développeur
“cache miss for user 42”
INFO
Événements normaux et attendus qui méritent d’être consignés
“request handled in 12 ms”
WARNING
Quelque chose d’inattendu, mais le programme continue
“disk space at 85%”
ERROR
Une opération a échoué — cette requête/tâche est cassée
“payment gateway timed out”
CRITICAL
Le programme lui-même risque de ne pas pouvoir continuer
On est tenté de croire que le logging n’est que print() avec des chichis en plus. Ce n’est pas le cas — trois différences changent votre façon de travailler :
print()
logging
Gravité
Chaque ligne se ressemble
Chaque ligne porte un niveau (DEBUG…CRITICAL)
Source
Vous ne savez pas quel module a affiché
Chaque ligne porte son nom de logger
Contrôle
Tout ou rien — vous supprimez des lignes pour les faire taire
Montez le niveau pour faire taire une catégorie, sans édition
Destination
Sortie standard uniquement
Console, un fichier, syslog, plusieurs à la fois — via les handlers
Cycle de vie
Vous les retirez avant de livrer
Elles restent dans le code ; la production tourne simplement plus silencieusement
La dernière ligne est le vrai gain. Le débogage par print est jetable — vous ajoutez des print, vous les lisez, vous les supprimez. Le logging est une instrumentation permanente : les lignes de diagnostic restent dans le code, silencieuses en production parce que le niveau est monté, et prêtes à être réactivées le jour où quelque chose casse. Vous ne réécrivez jamais le débogage que vous aviez supprimé le mois dernier.
La vraie structure : logger → handler → formatter
basicConfig convient pour un script, mais dès que vous voulez « DEBUG dans un fichier, INFO dans la console » il vous faut les trois pièces mobiles qui se cachent en dessous. C’est la structure que tout le monde rate, alors nommons chaque partie :
Un logger est l’objet que vous appelez (log.info(...)). Obtenez-en un par son nom avec logging.getLogger("checkout") — ou, dans un vrai module, logging.getLogger(__name__), qui utilise le nom du module lui-même pour que chaque ligne de log dise d’où elle vient. Les loggers sont des singletons par nom : demandez "checkout" deux fois et vous obtenez le même objet.
Un handler décide où va un enregistrement : StreamHandler écrit dans la console, FileHandler écrit dans un fichier. Un même logger peut avoir plusieurs handlers, chacun avec son propre niveau.
Un formatter décide de l’apparence de chaque ligne — un gabarit de style % de champs comme %(levelname)s, %(name)s, %(message)s, et %(asctime)s pour un horodatage.
Voici un logger avec deux handlers : la console affiche INFO et au-dessus, tandis qu’un fichier enregistre tout à partir de DEBUG — le terminal reste donc lisible mais le détail complet est sur le disque. (Nous journalisons dans un fichier temporaire pour que la leçon reste autonome ; dans votre propre application vous passeriez un vrai chemin comme "app.log".)
import logging, sys, tempfile, oslog = logging.getLogger("etl") # named logger (a singleton by name)log.handlers.clear() # reset so this block runs cleanly on its ownlog.propagate =False# don't also bubble up to the root loggerlog.setLevel(logging.DEBUG) # the logger passes DEBUG and up to its handlersconsole = logging.StreamHandler(sys.stdout) # handler 1: the consoleconsole.setLevel(logging.INFO) # ...but only INFO and above hereconsole.setFormatter(logging.Formatter("%(levelname)-8s%(message)s"))log.addHandler(console)fd, path = tempfile.mkstemp(suffix=".log") # a throwaway file for the demoos.close(fd)logfile = logging.FileHandler(path, mode="w")logfile.setLevel(logging.DEBUG) # handler 2: the file records EVERYTHINGlogfile.setFormatter(logging.Formatter("%(levelname)-8s%(name)s: %(message)s"))log.addHandler(logfile)log.debug("connecting to source") # file only (below the console's INFO)log.info("loaded 1000 rows") # console AND filelog.warning("42 rows had missing keys") # console AND filelogfile.close()print("---- what the file captured (DEBUG and up) ----")withopen(path) as f:print(f.read(), end="")os.remove(path)
INFO loaded 1000 rows
WARNING 42 rows had missing keys
---- what the file captured (DEBUG and up) ----
DEBUG etl: connecting to source
INFO etl: loaded 1000 rows
WARNING etl: 42 rows had missing keys
Observez ce qu’a fait chaque handler. La console n’a affiché que deux lignes — INFO et WARNING — parce que son niveau est INFO, donc la ligne DEBUG ne l’a jamais atteinte. Le fichier a capturé les trois, y compris la ligne DEBUG que la console a supprimée. Même logger, mêmes trois appels ; les deux handlers ont filtré différemment. C’est là le bénéfice de la structure : un logger, plusieurs destinations, chacune à sa propre verbosité.
Deux règles qui font trébucher. D’abord, un enregistrement doit franchir deux portes — le niveau du logger, puis le niveau du handler — donc régler le logger sur DEBUG est ce qui permet aux enregistrements DEBUG d’atteindre les handlers ; chaque handler applique ensuite son propre seuil. Ensuite, propagate = False : par défaut, un logger nommé transmet aussi ses enregistrements vers le haut, au logger racine qui (si vous avez appelé basicConfig plus tôt) a son propre handler — vous verriez donc chaque ligne deux fois. Couper la propagation, ou ne pas configurer le logger racine, garde la sortie propre. (Ce double affichage est le bug classique « pourquoi mes logs sont-ils dupliqués ? » — voir Problèmes fréquents.)
En production, vous ajouteriez généralement un horodatage avec le champ %(asctime)s — logging.Formatter("%(asctime)s %(levelname)-8s %(name)s: %(message)s") produit des lignes comme 2026-07-18 14:03:11,204 INFO etl: loaded 1000 rows. Nous l’omettons de la sortie exécutable ci-dessus seulement parce que l’heure exacte change à chaque exécution ; dans un vrai log, c’est la première chose que vous voulez. Pour les fichiers tournants, syslog, les logs JSON et d’autres recettes, le Logging Cookbook est la référence.
Régler la verbosité
Voici l’habitude qui rend le logging payant. Le même code instrumenté tourne bruyant pendant que vous déboguez et silencieux en production — vous changez un nombre, pas le code. Réglez le niveau du logger sur WARNING et chaque appel DEBUG et INFO se tait, tout en restant là, dans le source :
import logging, sysdef configure(name, level): log = logging.getLogger(name) log.handlers.clear() log.propagate =False log.setLevel(level) # <- the one dial h = logging.StreamHandler(sys.stdout) h.setFormatter(logging.Formatter("%(levelname)-8s%(name)s: %(message)s")) log.addHandler(h)return logdef run(log): log.debug("opening connection") log.info("processing batch of 500") log.warning("3 records skipped") log.error("batch failed to commit")print("=== development: level = DEBUG (everything) ===")run(configure("job", logging.DEBUG))print("\n=== production: level = WARNING (only WARNING and up) ===")run(configure("job", logging.WARNING))
=== development: level = DEBUG (everything) ===
DEBUG job: opening connection
INFO job: processing batch of 500
WARNING job: 3 records skipped
ERROR job: batch failed to commit
=== production: level = WARNING (only WARNING and up) ===
WARNING job: 3 records skipped
ERROR job: batch failed to commit
Des appels run() identiques, deux images différentes. En DEBUG vous voyez toute l’histoire — connexion, progression, les enregistrements ignorés, l’échec. En WARNING le bavardage de routine disparaît et il ne reste que les deux choses pour lesquelles vous réveilleriez vraiment quelqu’un. Rien dans run() n’a changé ; le seul setLevel a fait tout le travail. C’est pourquoi vous instrumentez une fois et ne supprimez jamais : dans une vraie application, le niveau vient d’une variable d’environnement ou d’un indicateur de configuration, si bien que le même build tourne verbeux en préproduction et laconique en production.
Débogage : lire la traceback, puis dégainer le débogueur
Le logging vous dit ce qui s’est passé juste avant une défaillance. Quand la défaillance est un plantage, Python vous tend une traceback — et bien la lire, c’est la moitié du débogage.
Lire une traceback de bas en haut
Une traceback est le rapport que Python affiche quand une exception non rattrapée déroule la pile d’appels. Elle paraît lourde du haut et intimidante, mais l’astuce est simple : lisez-la depuis le bas.
Traceback (most recent call last): File "checkout.py", line 42, in<module> total = checkout(cart) File "checkout.py", line 31, in checkoutreturn apply_discount(subtotal, code) File "checkout.py", line 19, in apply_discount rate = DISCOUNTS[code]KeyError: 'SUMMER25'
Commencez par la dernière ligne : KeyError: 'SUMMER25' — c’est l’erreur réelle, un type d’exception et son message. Un KeyError signifie qu’on a demandé à un dictionnaire une clé qu’il n’a pas, et la clé était 'SUMMER25'. Regardez maintenant le cadre juste au-dessus — File "checkout.py", line 19, in apply_discount, rate = DISCOUNTS[code] — c’est où ça a explosé : ligne 19, lors de la recherche de DISCOUNTS['SUMMER25']. Chaque cadre au-dessus est celui qui l’a appelé : apply_discount a été appelé par checkout (ligne 31), lui-même appelé au niveau supérieur (ligne 42). Un cadre de pile (stack frame) est une de ces entrées — un appel de fonction qui était en cours quand l’erreur s’est produite.
L’ordre de lecture est donc : la ligne du bas = ce qui a mal tourné ; le cadre au-dessus = où ; les cadres encore au-dessus = le chemin d’appels qui vous y a mené. La ligne du haut (Traceback (most recent call last)) vous dit littéralement que le plus récent — le coupable — est en bas. Neuf fois sur dix, vous corrigez le bug au niveau de l’avant-dernier cadre.
Poser un breakpoint() pour inspecter l’état
Une traceback est un instantané du plantage. Parfois vous devez fouiller avant lui — inspecter des variables, avancer ligne par ligne. C’est le débogueur, et depuis Python 3.7 vous l’invoquez avec une seule fonction intégrée, breakpoint() :
def apply_discount(subtotal, code):breakpoint() # execution pauses HERE, drops into the debugger rate = DISCOUNTS[code]return subtotal * (1- rate)
Lancez le programme : il s’arrête à cette ligne et vous tend une invite interactive pdb ((Pdb)), où vous pouvez taper des noms de variables pour voir leurs valeurs, évaluer des expressions et avancer. breakpoint() est interactif, il ne peut donc pas s’exécuter sur cette page — essayez-le dans un script local. Voici les commandes que vous utiliserez quatre-vingt-dix pour cent du temps :
Commande
Fait
p code
Affiche (print) la valeur de code (ou de toute expression)
n
Ligne suivante (next : exécute la ligne courante, reste dans cette fonction)
s
Entre (step) dans l’appel de fonction de cette ligne
c
Continue jusqu’au prochain point d’arrêt ou la fin
l
Liste (list) le source autour de la ligne courante
w
Where — affiche la pile courante (la traceback jusqu’ici)
q
Quitte (quit) le débogueur et abandonne
Référence complète des commandes : la documentation pdb et la fonction intégrée breakpoint(). Une astuce de plus : breakpoint() respecte la variable d’environnement PYTHONBREAKPOINT — mettez PYTHONBREAKPOINT=0 et chaque appel breakpoint() est ignoré, ce qui vous permet de les laisser en place et de tous les désactiver d’un coup.
Journalisez votre chemin jusqu’au bug
Souvent, vous ne voulez pas du tout arrêter le programme — vous voulez voir le chemin qu’il a pris. C’est encore le logging, en DEBUG : parsemez log.debug(...) dans le code suspect, exécutez-le, et lisez la trace. Et quand vous rattrapez une exception, log.exception(...) enregistre le message plus la traceback complète, si bien qu’une erreur rattrapée laisse quand même un enregistrement complet :
log = logging.getLogger(__name__)try: total = checkout(cart)exceptKeyError: log.exception("checkout failed for cart %s", cart_id) # logs the message AND the tracebackraise
log.exception ne fonctionne qu’à l’intérieur d’un bloc except (il lit l’exception courante), et il journalise au niveau ERROR avec la traceback attachée — la façon honnête de consigner une défaillance que vous gérez plutôt que de planter dessus.
Quand une IA écrit votre logging
Demandez à un assistant de code d’« ajouter du logging » et vous obtiendrez d’ordinaire l’une de deux réponses minces : une poignée d’instructions print() rebaptisées logging.info(...), ou un simple logging.basicConfig() et rien de plus. Les deux passent à côté de ce qui rend le logging utile. Le jugement que le modèle saute est la partie que vous possédez désormais : choisir un niveau qui correspond à la gravité (pas INFO pour tout), utiliser un logger nommé (getLogger(__name__)) pour savoir d’où vient une ligne, et recourir aux handlers quand vous avez besoin de la console et d’un fichier à des verbosités différentes. Un extrait généré est un bon point de départ — mais c’est vous qui décidez des niveaux, des noms, et si le débogage par print doit devenir une instrumentation permanente. La cellule exécutable ci-dessous est là où vous vérifiez que le logging dit bien ce que vous vouliez.
Essayez en direct
Les blocs ci-dessus se sont exécutés au moment du build. Modifiez celui-ci et appuyez sur Run pour changer le niveau de log et regarder les messages apparaître et disparaître — il s’exécute dans votre navigateur via Pyodide (aucune installation). logging est de la pure bibliothèque standard, donc cela s’exécute instantanément, sans rien à télécharger.
🟢 Avec un agent IA
Vous supprimez encore des instructions print() avant chaque commit, ou vous fixez une traceback en vous demandant quelle ligne compte ? Collez votre code et demandez à Prova« transforme ce débogage par print en un vrai logging avec des niveaux sensés » — ou collez une traceback et demandez « quel cadre est le vrai bug ? ». Elle met en place un logger nommé avec un handler et un formatter, choisit un niveau par message, et remonte la traceback de bas en haut jusqu’au coupable. Parce que le runtime est juste là, vous l’exécutez et regardez les bonnes lignes apparaître au bon niveau — vous ne faites pas confiance à la réécriture, vous la prouvez. The runtime is the judge. Demandez à Prova →
La même idée en R
Vous travaillez dans les deux langages ? R propose les mêmes outils sous d’autres noms. Pour le logging à niveaux, les paquets logger et logging vous donnent les niveaux DEBUG/INFO/WARN/ERROR, des loggers nommés et des handlers (« appenders ») qui écrivent dans la console ou un fichier — l’analogue direct du logging de Python. Pour des diagnostics rapides, le message() de R base (écrit sur stderr, comme un log) et warning() correspondent à INFO et WARNING. Pour lire une défaillance, R affiche une pile d’appels que vous inspectez avec traceback() après une erreur, et vous plongez dans le débogueur interactif de R avec browser() — exactement le rôle que joue breakpoint() en Python. Le concept se transpose : journalisez avec un niveau pour pouvoir monter ou baisser le volume, et entrez dans un débogueur pour inspecter l’état au point de défaillance. Voir le pilier Programmation pour le versant R.
Problèmes fréquents
Votre logging.info() n’affiche rien. Le niveau par défaut du logger racine est WARNING, donc les appels INFO et DEBUG sont sous le seuil et silencieusement écartés. Appelez logging.basicConfig(level=logging.INFO) (ou logging.DEBUG) une fois au démarrage, ou fixez le niveau de votre logger avec log.setLevel(logging.INFO). Rien n’est cassé — les messages sont simplement filtrés.
Vos messages de log n’apparaissent pas du tout, même en DEBUG. Par défaut, logging écrit sur la sortie d’erreur standard, qu’une cellule de notebook ou un contexte à sortie capturée peut ne pas afficher. Pointez-le vers la sortie standard — logging.basicConfig(stream=sys.stdout, ...), ou ajoutez un StreamHandler(sys.stdout) — et les messages apparaissent. (Dans un vrai terminal, stderr et stdout sont tous deux visibles, donc cela mord surtout dans les notebooks et les rendus web.)
Chaque ligne de log s’affiche deux fois (ou trois fois). Vous avez des handlers dupliqués ou une propagation vers un logger racine configuré. Réexécuter une cellule de configuration qui fait addHandler ajoute un handler de plus à chaque fois — alors videz d’abord avec log.handlers.clear(). Et un logger nommé transmet ses enregistrements vers le haut, au logger racine, par défaut ; si le logger racine a lui aussi un handler (parce que vous avez appelé basicConfig), vous obtenez une copie par niveau. Mettez log.propagate = False, ou ne configurez qu’un seul des deux loggers.
Questions fréquentes
NoteQuelle est la différence entre logging et print en Python ?
print() écrit une ligne sans étiquette sur la sortie standard, et c’est tout ce qu’il fait. Un message de log porte un niveau (DEBUG, INFO, WARNING, ERROR, CRITICAL) qui classe sa gravité et un nom de logger qui dit d’où il vient, et vous contrôlez quels messages apparaissent — et où ils vont — depuis un seul endroit. La différence pratique : vous supprimez les instructions print() avant de livrer, mais les lignes de logging restent dans le code, silencieuses en production parce que le niveau est monté, et prêtes à être réactivées quand quelque chose casse. Utilisez print() pour la sortie du programme qu’un utilisateur doit voir ; utilisez le logging pour les diagnostics sur le fonctionnement du programme.
NoteQuels sont les niveaux de logging de Python ?
Cinq, du moins au plus grave : DEBUG (diagnostics détaillés pour le développeur), INFO (événements normaux et attendus), WARNING (quelque chose d’inattendu mais le programme continue), ERROR (une opération a échoué) et CRITICAL (le programme risque de ne pas pouvoir continuer). Chaque niveau a une valeur numérique (DEBUG=10 … CRITICAL=50). Vous fixez un seuil avec setLevel, et tout message en dessous est écarté — ainsi setLevel(logging.WARNING) affiche WARNING, ERROR et CRITICAL, et fait taire DEBUG et INFO.
NoteComment journaliser dans un fichier en Python ?
Ajoutez un FileHandler à votre logger : handler = logging.FileHandler("app.log"), donnez-lui un formatter avec handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")), puis logger.addHandler(handler). Pour la voie rapide, logging.basicConfig(filename="app.log", level=logging.INFO) configure le logger racine pour écrire dans un fichier en un seul appel. Vous pouvez ajouter aussi un StreamHandler pour journaliser à la fois dans la console et dans un fichier, chacun à son propre niveau — c’est la configuration de production courante.
NoteComment lire une traceback Python ?
De bas en haut. La dernière ligne est l’exception réelle — son type et son message, par exemple KeyError: 'SUMMER25'. Le cadre juste au-dessus indique où l’erreur s’est produite (le fichier, le numéro de ligne et le code). Chaque cadre au-dessus est l’appel qui y a mené, le plus ancien en haut. Lisez donc la ligne du bas pour savoir ce qui a mal tourné, le cadre juste au-dessus pour savoir où, et remontez seulement si vous avez besoin du chemin qui y a conduit. L’en-tête « most recent call last » vous dit que le coupable se trouve en bas.
NoteComment régler le niveau de logging en Python ?
Appelez setLevel sur le logger — logging.getLogger("myapp").setLevel(logging.DEBUG) — ou, pour le logger racine, passez-le à logging.basicConfig(level=logging.INFO). Les handlers ont aussi leur propre setLevel, donc un enregistrement doit franchir à la fois le niveau du logger et celui du handler pour être émis. Un motif courant consiste à lire le niveau depuis une variable d’environnement pour que le même code tourne verbeux en développement et silencieux en production : level = os.environ.get("LOG_LEVEL", "INFO"), puis logger.setLevel(level).
Testez vos connaissances
ImportantVotre tour — construire un logger à deux destinations
Mettez en place un seul logger nommé qui écrit à deux verbosités : tout dans un fichier, mais seulement les lignes importantes dans la console.
Tâche 1. Créez un logger nommé "orders". Donnez-lui un StreamHandler (vers sys.stdout) au niveau WARNING et un FileHandler au niveau DEBUG, chacun avec un formatter. Journalisez un message à chacun des niveaux DEBUG, INFO, WARNING et ERROR, puis relisez le fichier et affichez-le. Vérifiez que la console n’affiche que les lignes WARNING et ERROR tandis que le fichier contient les quatre.
Tâche 2. En une phrase, expliquez pourquoi le niveau propre du logger doit être DEBUG pour que le handler de fichier reçoive la ligne DEBUG.
AstuceIndice
Un enregistrement doit franchir deux portes : le niveau du logger, puis le niveau de chaque handler. Mettez log.setLevel(logging.DEBUG) pour que le logger laisse tout passer — puis chaque handler applique son propre seuil (console.setLevel(logging.WARNING), filehandler.setLevel(logging.DEBUG)). Pensez à log.handlers.clear() et log.propagate = False pour que le bloc soit autonome et que rien ne s’affiche en double.
AstuceSolution
import logging, sys, tempfile, oslog = logging.getLogger("orders")log.handlers.clear()log.propagate =Falselog.setLevel(logging.DEBUG) # let everything through to the handlersconsole = logging.StreamHandler(sys.stdout)console.setLevel(logging.WARNING) # console: only WARNING and upconsole.setFormatter(logging.Formatter("%(levelname)-8s%(message)s"))log.addHandler(console)fd, path = tempfile.mkstemp(suffix=".log")os.close(fd)filehandler = logging.FileHandler(path, mode="w")filehandler.setLevel(logging.DEBUG) # file: everythingfilehandler.setFormatter(logging.Formatter("%(levelname)-8s%(name)s: %(message)s"))log.addHandler(filehandler)log.debug("validating order 88")log.info("order 88 accepted")log.warning("order 88 shipping address incomplete")log.error("order 88 payment declined")filehandler.close()withopen(path) as f:print(f.read(), end="")os.remove(path)
La console n’affiche que les lignes WARNING et ERROR (son niveau est WARNING) ; le fichier contient les quatre (son niveau est DEBUG). Tâche 2 : le niveau du logger est la première porte — s’il était réglé au-dessus de DEBUG, les enregistrements DEBUG seraient écartés avant même d’atteindre le moindre handler, si bien que le niveau DEBUG propre au handler de fichier n’aurait jamais l’occasion de les accepter.
Vérification rapide. Vous appelez logging.info("app started") en haut d’un script tout neuf et rien ne s’affiche. Pourquoi ?
AstuceAfficher la réponse
Le niveau par défaut du logger racine est WARNING, donc un message INFO est sous le seuil et silencieusement écarté — le code va bien, il est juste filtré. Configurez d’abord le logging : logging.basicConfig(level=logging.INFO) (ou logging.DEBUG) abaisse le seuil pour que INFO apparaisse. Si ça ne s’affiche toujours pas dans un notebook, ajoutez stream=sys.stdout — la destination par défaut est la sortie d’erreur standard.
Conclusion
Vous savez maintenant journaliser comme un professionnel au lieu de parsemer des print(). Le logging bat print parce que chaque ligne porte un niveau et une source, et vous activez ou désactivez des catégories depuis un seul endroit au lieu de supprimer du code. Les cinq niveaux — DEBUG, INFO, WARNING, ERROR, CRITICAL — forment un cadran que vous réglez avec setLevel. Sous basicConfig se cache une structure en trois parties qui mérite d’être connue : un logger que vous appelez (getLogger(__name__)), des handlers qui décident où vont les enregistrements, et des formatters qui décident de leur apparence — de quoi journaliser à la fois dans la console et dans un fichier, chacun à sa propre verbosité. Et quand quelque chose casse, lisez la traceback de bas en haut jusqu’à l’erreur réelle, et posez un breakpoint() pour inspecter l’état au point de défaillance. Instrumentez une fois, gardez-le, et tournez silencieux en production.