Journalisation B : enregistrer les journaux dans un fichier et consigner les actions importantes
- Mettre en place un logging qui écrit dans un fichier et comprendre pourquoi le journal « survit » d’une exécution à l’autre
- Distinguer le mode ajout (append) et le mode écriture (écrasement)
- Enregistrer une Exception avec le Traceback complet grâce à
logging.exception - Décider quelles actions il est important de consigner et à quel niveau
Qu’allons-nous apprendre ?
- Comment écrire les journaux dans un fichier, et pas seulement à l’écran.
- Les modes d’écriture : ajouter à la fin du fichier, ou repartir de zéro.
- Comment enregistrer une erreur avec tous les détails (Traceback).
- Quoi consigner : quelles actions sont importantes et à quel niveau.
Pourquoi en avons-nous besoin ?
Vous vous souvenez du thème sur l’ordinateur, le système d’exploitation et le réseau ? Ce qui est à l’écran et en mémoire disparaît à la fin du programme. Un fichier est conservé sur le stockage. Un journal à l’écran n’aide que celui qui est assis devant maintenant. Un journal dans un fichier peut être ouvert demain, fouillé, et utilisé pour comprendre ce qui s’est passé pendant la nuit.
Sur les vrais serveurs, les journaux sont le principal outil d’investigation des pannes. « Donne-moi les journaux de 3 heures du matin » est une phrase courante dans une équipe de développement.
Explication à partir des bases
Journaliser dans un fichier
On ajoute le paramètre filename à basicConfig. À partir de là, les enregistrements sont écrits dans le fichier au lieu de l’écran. Le fichier est créé automatiquement s’il n’existe pas, dans le dossier courant (le Current Working Directory, comme tout chemin relatif).
Les modes d’écriture (filemode)
| Mode | Signification | Quand |
|---|---|---|
"a" (Append) — par défaut | Ajoute à la fin du fichier. Les journaux précédents sont conservés. | Presque toujours. On veut un historique. |
"w" (Write) | Supprime le contenu précédent et repart de zéro à chaque exécution | En pratique, quand on ne veut que la dernière exécution |
logging.exception — une erreur avec tous les détails
logging.error("Payment failed") n’enregistre que le message. Dans un bloc except, on peut utiliser logging.exception("...") : il enregistre au niveau ERROR et joint aussi le Traceback complet, si bien que le fichier montre exactement à quelle ligne et dans quelle fonction l’erreur s’est produite. Comme si vous aviez vu le plantage vous-même, sans que le programme plante.
Qu’est-ce qui vaut la peine d’être consigné ?
| À consigner | Pourquoi |
|---|---|
| Le démarrage et l’arrêt du programme/serveur | Pour savoir quand il a tourné et quand il s’est arrêté |
| Les actions qui modifient des données : création, mise à jour, suppression | « Qui a supprimé la commande ? » |
| Les connexions et déconnexions, les tentatives échouées | La sécurité |
Chaque erreur attrapée (except) | Pour ne pas faire disparaître les problèmes |
| Les appels à des services externes et leur résultat | « Le paiement a-t-il été envoyé ? » |
| À ne pas consigner | Pourquoi |
|---|---|
| Les mots de passe, les numéros de carte bancaire, les jetons | La sécurité et la vie privée |
| Chaque tour d’une grande boucle en INFO | Du « bruit » qui cache l’essentiel. Si nécessaire, en DEBUG |
Syntaxe
import logging
logging.basicConfig(
filename="app.log", # write to this file (relative to the working directory)
filemode="a", # "a" = append (default), "w" = overwrite each run
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8", # safe for any characters in messages
)
try:
risky()
except SomeError:
logging.exception("What we were trying to do") # ERROR + full tracebackPremier exemple de code
week-2/file_logger.py :
import logging
logging.basicConfig(
filename="app.log",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
logging.info("Program started")
logging.warning("Low disk space")
logging.info("Program finished")
print("Done. Check app.log")À l’écran :
Done. Check app.logDans le fichier app.log (après deux exécutions) :
2026-10-06 10:02:41,203 INFO Program started
2026-10-06 10:02:41,203 WARNING Low disk space
2026-10-06 10:02:41,204 INFO Program finished
2026-10-06 10:03:15,881 INFO Program started
2026-10-06 10:03:15,881 WARNING Low disk space
2026-10-06 10:03:15,882 INFO Program finishedAnalyse du code ligne par ligne
| Ligne | Explication |
|---|---|
filename="app.log" | Les journaux sont écrits dans ce fichier, et non à l’écran. |
(pas de filemode) | La valeur par défaut est "a" : chaque exécution ajoute à la fin du fichier. C’est pourquoi après deux exécutions il y a 6 lignes. |
encoding="utf-8" | Permet d’écrire n’importe quel caractère, y compris des lettres non anglaises dans les messages (dans notre code les messages sont en anglais, mais les noms d’utilisateur peuvent être dans n’importe quelle langue). |
print("Done...") | print arrive toujours à l’écran. C’est une sortie pour l’utilisateur, sans rapport avec le journal. |
Pour lire le fichier : ouvrez-le dans VS Code, ou depuis le terminal avec cat app.log. Dans PowerShell, on peut aussi le suivre en temps réel : Get-Content app.log -Wait. Sur Mac/Linux : tail -f app.log.
Un autre exemple
Un journal complet pour un programme qui traite des paiements, avec logging.exception. week-2/payments.py :
import logging
logging.basicConfig(
filename="payments.log",
filemode="w",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
def charge(customer, amount_text):
amount = float(amount_text)
if amount <= 0:
raise ValueError("amount must be positive, got " + str(amount))
logging.info("Charged %s: %.2f NIS", customer, amount)
return amount
payments = [("dana", "120"), ("avi", "abc"), ("noa", "-5"), ("yossi", "80.5")]
logging.info("Batch started: %s payments", len(payments))
total = 0
failed = 0
for customer, amount_text in payments:
try:
total = total + charge(customer, amount_text)
except ValueError:
failed = failed + 1
logging.exception("Payment failed for %s (input: %r)", customer, amount_text)
logging.info("Batch finished: total %.2f NIS, %s failed", total, failed)
print("Batch done. Total:", total, "| Failed:", failed)À l’écran :
Batch done. Total: 200.5 | Failed: 2Dans payments.log :
2026-10-06 10:20:01,010 INFO Batch started: 4 payments
2026-10-06 10:20:01,010 INFO Charged dana: 120.00 NIS
2026-10-06 10:20:01,011 ERROR Payment failed for avi (input: 'abc')
Traceback (most recent call last):
File "payments.py", line 28, in <module>
total = total + charge(customer, amount_text)
File "payments.py", line 13, in charge
amount = float(amount_text)
ValueError: could not convert string to float: 'abc'
2026-10-06 10:20:01,012 ERROR Payment failed for noa (input: '-5')
Traceback (most recent call last):
File "payments.py", line 28, in <module>
total = total + charge(customer, amount_text)
File "payments.py", line 15, in charge
raise ValueError("amount must be positive, got " + str(amount))
ValueError: amount must be positive, got -5.0
2026-10-06 10:20:01,012 INFO Charged yossi: 80.50 NIS
2026-10-06 10:20:01,012 INFO Batch finished: total 200.50 NIS, 2 failedAnalyse :
filemode="w"— ici, nous avons choisi d’écraser à chaque exécution, pour qu’il n’y ait en pratique que le dernier journal.%.2f— un nombre décimal avec 2 chiffres après la virgule (120.00). Adapté aux montants d’argent.%r— affiche la valeur avec des guillemets ('abc'). Ainsi, on voit clairement ce qui a été reçu, y compris les espaces en trop ou une chaîne vide.logging.exception(...)— un enregistrement ERROR, avec le Traceback complet en dessous. Les deux erreurs sont desValueError, mais pour des raisons différentes. Le fichier montre exactement quelle ligne a lancé chacune :float()(ligne 13) ou notreraise(ligne 15).- Le programme n’a pas planté : il passe au paiement suivant, et toutes les informations pour enquêter sont conservées.
printrésume pour l’utilisateur en une ligne. Le journal raconte toute l’histoire au développeur.
Erreur courante
Erreur 1 : « le journal n’est pas écrit / je ne trouve pas le fichier ». Le fichier est créé dans le Current Working Directory. Si vous avez lancé depuis un autre dossier, le fichier est là-bas. (Souvenez-vous du thème sur l’ordinateur et le système d’exploitation !)
Erreur 2 : logging.error dans un except quand on veut enquêter.
except ValueError:
logging.error("Payment failed")Cela dit que ça a échoué, mais ni pourquoi ni où. Dans un except, logging.exception donne le tableau complet sans effort. (Si l’erreur est attendue et ordinaire, comme une mauvaise saisie d’un utilisateur, et que vous n’avez pas besoin de Traceback, vous pouvez utiliser logging.warning avec le message de l’erreur : logging.warning("bad input: %s", error).)
Erreur 3 : filemode="w" sur un vrai serveur. Chaque redémarrage du serveur efface l’historique. Juste après un plantage, quand on a le plus besoin du journal, il est effacé. Sur les serveurs : uniquement "a".
Erreur 4 : croire que logging.exception gère l’erreur. Il ne fait que l’enregistrer. La décision de quoi faire (continuer, réessayer, la transmettre avec raise) reste la vôtre.
Exercices
Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.
Exercice 1: Un journal dans un fichier
Mettez en place un journal enregistré dans le fichier shop.log, au niveau INFO, avec l’heure, le niveau et le message, et qui n’est pas effacé d’une exécution à l’autre. Consignez-y :
- INFO quand la commande numéro 17 est créée ;
- WARNING quand il ne reste que 2 unités en stock.
Afficher la solution
import logging
logging.basicConfig(
filename="shop.log",
filemode="a",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
logging.info("order 17 created")
logging.warning("low stock: 2 left")filename— le journal est écrit dans un fichier au lieu de l’écran.filemode="a"— ajout (append) : chaque exécution ajoute à la fin du fichier, et l’historique est conservé.- Le contenu du fichier ressemblera à ceci :
2026-11-01 10:00:01 INFO order 17 created
2026-11-01 10:00:01 WARNING low stock: 2 left
Exercice 2: Ajout face à écriture
- Quelle est la différence entre
filemode="a"etfilemode="w"? - Un serveur a planté pendant la nuit et a été redémarré automatiquement. Le journal était configuré avec
"w". Quel est le problème ?
Afficher la solution
"a"(append) — ajoute à la fin du fichier existant."w"(write) — supprime le fichier et repart de zéro à chaque exécution.- Avec
"w", le redémarrage a effacé le journal — exactement l’information nécessaire pour comprendre pourquoi le serveur a planté. Pour les journaux, on utilise presque toujours"a".
Exercice 3: logging.exception
Écrivez une boucle qui encaisse une liste de paiements. Chaque montant est un texte ; un montant qui n’est pas un nombre est consigné dans le journal avec tous les détails de l’erreur, et la boucle continue. À la fin, le montant total est affiché.
payments = ["120", "abc", "80"]Résultat attendu :
Total charged: 200Afficher la solution
import logging
logging.basicConfig(filename="payments.log", filemode="a", level=logging.INFO, encoding="utf-8")
payments = ["120", "abc", "80"]
total = 0
for amount_text in payments:
try:
total = total + int(amount_text)
logging.info("charged %s", amount_text)
except ValueError:
logging.exception("invalid amount: %s", amount_text)
print("Total charged:", total)Total charged: 200logging.exception est appelé dans le except. Il enregistre au niveau ERROR, et ajoute le Traceback complet au journal — ainsi on voit exactement où et pourquoi le montant a échoué.
Exercice 4: Quoi consigner et quoi ne pas consigner
Pour chaque élément — le consigner dans le journal ou non ? Expliquez.
- Le numéro de la commande qui a échoué.
- Le mot de passe tapé par l’utilisateur, quand la connexion a échoué.
- Le nom d’utilisateur qui a essayé de se connecter.
- Le numéro complet de la carte bancaire.
- Le temps qu’a pris une action lente.
Afficher la solution
- À consigner — sans lui, on ne peut pas enquêter sur la panne.
- À ne pas consigner — les mots de passe ne doivent jamais apparaître dans le journal. Quiconque peut lire le journal les verrait.
- À consigner — important pour la sécurité : qui a essayé de se connecter, et quand.
- À ne pas consigner — une information sensible. Au plus les quatre derniers chiffres.
- À consigner — cela aide à trouver des problèmes de performance.
La règle : assez de détails pour enquêter — mais jamais d’informations sensibles.
Exercice 5: Un journal de connexions
Vous avez une liste de tentatives de connexion. Chaque tentative est un nom d’utilisateur et un mot de passe (deux listes parallèles). Le mot de passe correct pour tous les utilisateurs de l’exercice est "1234". Consignez dans le journal security.log :
- INFO pour chaque connexion réussie ;
- WARNING pour chaque mauvais mot de passe (sans consigner le mot de passe !).
À la fin, affichez combien de connexions ont réussi et combien ont échoué.
users = ["dana", "avi", "dana", "noa"]
passwords = ["1234", "0000", "1111", "1234"]Résultat attendu :
Successful: 2, failed: 2Afficher la solution
import logging
CORRECT_PASSWORD = "1234"
logging.basicConfig(
filename="security.log",
filemode="a",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
users = ["dana", "avi", "dana", "noa"]
passwords = ["1234", "0000", "1111", "1234"]
successful = 0
failed = 0
for i in range(len(users)):
if passwords[i] == CORRECT_PASSWORD:
successful = successful + 1
logging.info("login ok: %s", users[i])
else:
failed = failed + 1
logging.warning("wrong password for %s", users[i])
logging.info("summary: %s successful, %s failed", successful, failed)
print("Successful: " + str(successful) + ", failed: " + str(failed))Successful: 2, failed: 2Remarquez : le WARNING consigne le nom d’utilisateur — et non le mot de passe.
Résumé
basicConfig(filename="app.log")écrit les journaux dans un fichier sur le stockage, et ils sont conservés après la fin du programme.filemode="a"(par défaut) ajoute."w"écrase. Sur les serveurs, toujours en ajout.logging.exception("...")dans unexceptenregistre une ERROR avec le Traceback complet, sans que le programme plante.- Consignez le démarrage/l’arrêt, les modifications de données, la sécurité et les erreurs. Ne consignez pas les mots de passe ni les informations sensibles.
- Le fichier est créé dans le Current Working Directory.
tail -f/Get-Content -Waitle suivent en temps réel.