# Journalisation A : pourquoi print n'est pas du logging, et les niveaux du journal<no value>

## Qu'allons-nous apprendre ?

- Ce qu'est un **journal** (Log) et pourquoi les vrais systèmes en écrivent un.
- Pourquoi **`print` n'est pas du logging**.
- Le module **`logging`** de Python.
- Les **cinq niveaux** du journal : `debug`, `info`, `warning`, `error`, `critical`.

## Pourquoi en avons-nous besoin ?

Le serveur que vous écrirez dans les thèmes backend et bases de données tournera **sans que personne ne le surveille**, peut-être sur un ordinateur dans le cloud, en pleine nuit. Demain matin, un client dira « ma commande a disparu pendant la nuit ». Comment saurez-vous ce qui s'est passé ?

- `print` a affiché sur un écran que personne n'a vu, et c'est déjà perdu.
- Le débogueur n'aide pas, car le bug s'est produit **hier**, pas maintenant.

**Le journal** est une « boîte noire » comme dans un avion : un enregistrement de ce qui s'est passé, quand, et avec quelle gravité. Tout système professionnel écrit des journaux, et les développeurs les lisent tous les jours.

## Explication à partir des bases

### Qu'est-ce qu'un journal ?

Un **journal** (Log) est une suite d'**enregistrements**. Chaque enregistrement décrit un événement : **quand** il s'est produit, **quelle est sa gravité**, et **ce qui** s'est passé.

```text
2026-10-06 09:14:02 INFO     User dana logged in
2026-10-06 09:14:30 WARNING  Login attempt with wrong password for avi
2026-10-06 09:15:11 ERROR    Payment failed for order 1042: card declined
```

### Pourquoi `print` n'est pas du logging

| | `print` | `logging` |
|---|---|---|
| **Heure** | Aucune. On ne sait pas quand c'est arrivé. | Automatique dans chaque enregistrement |
| **Gravité** | Tous les messages se ressemblent | 5 niveaux. On peut filtrer |
| **Désactiver/activer** | Il faut supprimer ou ajouter des lignes de code | Changer **un seul réglage**, sans toucher au code |
| **Destination** | Seulement l'écran | L'écran, un **fichier**, un serveur de journaux... |
| **Sur un serveur** | Se perd | Conservé et consulté plus tard |

**Règle :** `print` sert à communiquer avec l'**utilisateur** (la sortie du programme). `logging` sert à communiquer avec les **développeurs et les équipes d'exploitation** (ce qui s'est passé à l'intérieur du programme).

### Les cinq niveaux

| Niveau | Nombre | Quand l'utiliser | Exemple |
|---|---|---|---|
| `DEBUG` | 10 | Détails techniques pour le développeur, pendant le développement et le débogage | `"cart contents: [...]"` |
| `INFO` | 20 | Événements normaux et importants — « tout va bien » | `"user dana logged in"` |
| `WARNING` | 30 | Quelque chose d'inhabituel, mais le système continue | `"disk 90% full"`, `"retrying request"` |
| `ERROR` | 40 | Une action **a échoué** | `"payment failed for order 1042"` |
| `CRITICAL` | 50 | Tout le système est en danger et risque de ne pas pouvoir continuer | `"database unreachable - shutting down"` |

### Le niveau minimum — le filtrage

Vous fixez un **niveau minimum**, et seuls les messages **de ce niveau et au-dessus** sont affichés :

- Pendant le développement : `DEBUG`. Vous voyez tout.
- Sur un vrai serveur (Production) : `INFO` ou `WARNING`. Seulement ce qui compte, sans « bruit ».

**La valeur par défaut de Python est `WARNING`.** Donc si vous ne configurez rien, les messages `debug` et `info` **ne s'affichent pas du tout**.

{{< box "analogy" "Analogie : le système d'alerte d'un hôpital" >}}
**DEBUG** = l'enregistrement complet de chaque mesure (seul le médecin chercheur le regarde). **INFO** = « le patient a reçu son déjeuner ». **WARNING** = « température un peu élevée, continuer à surveiller ». **ERROR** = « un médicament n'a pas été donné à l'heure ». **CRITICAL** = « code bleu ! ». L'infirmière de nuit choisit de ne voir que WARNING et au-dessus, sinon les messages la submergeraient.
{{< /box >}}

## Syntaxe

```python
import logging

# configure once, at the start of the program
logging.basicConfig(
    level=logging.INFO,                                  # minimum level to show
    format="%(asctime)s %(levelname)s %(message)s",      # how each record looks
)

# write log records
logging.debug("technical details")
logging.info("normal important event")
logging.warning("something unusual")
logging.error("an operation failed")
logging.critical("the whole system is in danger")
```

**Champs courants dans `format` :**

| Champ | Ce qui est affiché |
|---|---|
| `%(asctime)s` | La date et l'heure |
| `%(levelname)s` | Le nom du niveau (`INFO`, `ERROR`...) |
| `%(message)s` | Votre message |
| `%(funcName)s` | Le nom de la fonction qui a écrit l'enregistrement |
| `%(lineno)d` | Le numéro de ligne |

## Premier exemple de code

`week-2/log_levels.py` :

```python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

logging.debug("This is a debug message")
logging.info("Application started")
logging.warning("Config file not found, using defaults")
logging.error("Could not connect to the payment service")
logging.critical("Out of memory - shutting down")
```

**Sortie :**

```text
2026-10-06 09:30:12,481 INFO Application started
2026-10-06 09:30:12,481 WARNING Config file not found, using defaults
2026-10-06 09:30:12,482 ERROR Could not connect to the payment service
2026-10-06 09:30:12,482 CRITICAL Out of memory - shutting down
```

## Analyse du code ligne par ligne

| Ligne | Explication |
|---|---|
| `import logging` | Le module intégré de Python pour les journaux. Aucune installation nécessaire. |
| `logging.basicConfig(...)` | Une configuration **unique** au début du programme : quel est le niveau minimum et à quoi ressemble chaque enregistrement. |
| `level=logging.INFO` | N'afficher qu'à partir d'INFO. |
| `format="..."` | Le modèle de l'enregistrement : heure, niveau, message. |
| `logging.debug(...)` | **Non affiché !** DEBUG (10) est inférieur à INFO (20), il a donc été filtré. |
| `logging.info(...)` à `logging.critical(...)` | Affichés, car ils sont au niveau INFO ou au-dessus. |
| `,481` dans la sortie | Les millisecondes. Utile quand de nombreux événements se produisent dans la même seconde. |

**Essayez :** passez à `level=logging.DEBUG` et relancez. Maintenant 5 lignes apparaissent. Avec `level=logging.ERROR`, seules 2 apparaissent. **Vous avez changé une seule ligne, sans toucher aux messages eux-mêmes.** C'est la force des niveaux.

## Un autre exemple

**Des journaux dans un vrai programme**, combinés avec les Exceptions des leçons précédentes. `week-2/orders.py` :

```python
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)-8s %(funcName)s: %(message)s",
)

STOCK = {"bread": 5, "milk": 0, "eggs": 12}

def place_order(product, quantity):
    logging.debug("place_order called with product=%s quantity=%s", product, quantity)

    if product not in STOCK:
        logging.error("Unknown product requested: %s", product)
        return False

    available = STOCK[product]
    if available == 0:
        logging.warning("Product out of stock: %s", product)
        return False

    if quantity > available:
        logging.warning("Not enough %s: requested %s, available %s", product, quantity, available)
        return False

    STOCK[product] = available - quantity
    logging.info("Order placed: %s x%s (left in stock: %s)", product, quantity, STOCK[product])
    return True

place_order("bread", 2)
place_order("milk", 1)
place_order("cake", 1)
place_order("eggs", 20)
```

**Sortie :**

```text
2026-10-06 09:41:03,115 DEBUG    place_order: place_order called with product=bread quantity=2
2026-10-06 09:41:03,115 INFO     place_order: Order placed: bread x2 (left in stock: 3)
2026-10-06 09:41:03,116 DEBUG    place_order: place_order called with product=milk quantity=1
2026-10-06 09:41:03,116 WARNING  place_order: Product out of stock: milk
2026-10-06 09:41:03,116 DEBUG    place_order: place_order called with product=cake quantity=1
2026-10-06 09:41:03,116 ERROR    place_order: Unknown product requested: cake
2026-10-06 09:41:03,117 DEBUG    place_order: place_order called with product=eggs quantity=20
2026-10-06 09:41:03,117 WARNING  place_order: Not enough eggs: requested 20, available 12
```

**Analyse :**

- `%(levelname)-8s` — le `-8` complète le nom du niveau jusqu'à 8 caractères, pour que les messages soient alignés en colonne. Facile à lire.
- `%(funcName)s` — vous voyez **de quelle fonction** vient chaque enregistrement.
- `logging.info("... %s ...", product, quantity)` — `%s` est un **emplacement** pour une valeur, et les valeurs sont passées comme arguments supplémentaires. C'est la façon recommandée en logging : le texte n'est construit que si le message sera vraiment affiché, donc les messages DEBUG filtrés ne coûtent presque rien.
- **Le choix des niveaux :** « commandé » est INFO (un événement normal et important). « En rupture de stock » est WARNING (l'activité continue, mais il vaut la peine de le savoir). « Le produit n'existe pas » est ERROR (quelque chose ne va pas dans la requête, peut-être un bug côté client). Les détails de l'appel sont en DEBUG.
- `if product not in STOCK` — `in` sur un dict vérifie si la **clé** existe.

Passez maintenant à `level=logging.WARNING` : le journal n'affichera que les 3 problèmes. C'est à cela qu'il ressemblera sur un vrai serveur, où seules les anomalies sont intéressantes.

## Erreur courante

**Erreur 1 : appeler `basicConfig` après qu'un message a déjà été écrit, ou deux fois.**

```python
import logging
logging.info("starting")                     # uses the default config!
logging.basicConfig(level=logging.INFO)      # ignored - already configured
logging.info("now?")                         # still not shown
```

Le premier appel à `logging.info` met automatiquement en place la configuration par défaut (`WARNING`), et un `basicConfig` tardif est silencieusement **ignoré**. **`basicConfig` toujours en premier, et une seule fois**, juste après l'`import`.

**Erreur 2 : « je ne vois pas mes info ».** La raison est presque toujours la valeur par défaut `WARNING`. La correction : `level=logging.INFO` (ou `DEBUG`).

**Erreur 3 : le mauvais niveau.**

```python
logging.error("User dana logged in")       # not an error!
logging.info("Payment failed!")            # this IS an error
```

Un mauvais niveau rend le filtrage inutile : celui qui ne regarde que les ERROR manquera un paiement échoué, et sera inondé de connexions ordinaires.

**Erreur 4 : journaliser des informations sensibles.** `logging.info("login: user=dana password=1234")` — **jamais** de mots de passe, de numéros de carte bancaire ni de jetons (tokens). Les journaux sont lus par beaucoup de monde et conservés longtemps.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## 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: Quel niveau ?

Pour chaque événement — à quel niveau de journal l'enregistreriez-vous : DEBUG, INFO, WARNING, ERROR ou CRITICAL ?

1. Un utilisateur s'est connecté avec succès.
2. La valeur d'une variable dans une boucle, pendant la recherche d'un bug.
3. Un utilisateur a tapé un mauvais mot de passe.
4. Le paiement a échoué.
5. Il n'y a pas de connexion à la base de données, et le serveur ne peut pas fonctionner du tout.

{{< solution >}}
1. **INFO** — un événement normal qu'il est important d'enregistrer.
2. **DEBUG** — des détails techniques qui ne sont utiles que pendant le développement.
3. **WARNING** — quelque chose d'inhabituel, mais le système continue de fonctionner.
4. **ERROR** — une action a échoué.
5. **CRITICAL** — tout le système ne peut pas fonctionner.
{{< /solution >}}

### Exercice 2: Le niveau minimum

```python
import logging

logging.basicConfig(level=logging.WARNING)

logging.debug("checking item 3")
logging.info("order 17 created")
logging.warning("stock is low: 2 left")
logging.error("payment failed for order 17")
```

Lesquels des messages apparaîtront, et pourquoi ? Que faut-il changer pour les voir tous ?

{{< solution >}}
Seuls **WARNING** et **ERROR** apparaîtront :

```text
WARNING:root:stock is low: 2 left
ERROR:root:payment failed for order 17
```

`level=logging.WARNING` est un **niveau minimum** : seuls les messages de ce niveau et au-dessus sont affichés. Pour les voir tous, passez à `level=logging.DEBUG`. Sans toucher au reste du code — c'est tout l'avantage.
{{< /solution >}}

### Exercice 3: De print à logging

Remplacez chaque `print` par le niveau de journal adapté, et définissez un format avec l'heure, le niveau et le message :

```python
def withdraw(balance, amount):
    print("withdraw called with", amount)
    if amount > balance:
        print("not enough money, balance is", balance)
        return balance
    print("withdrew", amount)
    return balance - amount
```

{{< solution >}}
```python
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)-8s %(message)s",
)


def withdraw(balance, amount):
    logging.debug("withdraw called with %s", amount)
    if amount > balance:
        logging.error("not enough money, balance is %s", balance)
        return balance
    logging.info("withdrew %s", amount)
    return balance - amount


withdraw(100, 30)
withdraw(100, 500)
```

- L'entrée dans la fonction — **DEBUG**, un retrait réussi — **INFO**, un échec — **ERROR**.
- Le format ajoute l'heure et le niveau à chaque ligne, par exemple : `2026-11-01 10:15:02 ERROR    not enough money, balance is 100`.
{{< /solution >}}

### Exercice 4: Lire une ligne de journal

```text
2026-11-01 09:14:30 WARNING  Wrong password for avi
```

1. Que représente chaque partie de la ligne, et quelle partie du réglage `format` la crée ?
2. Pourquoi est-il important que la ligne contienne une heure ?

{{< solution >}}
1. Les parties :
   - `2026-11-01 09:14:30` — l'heure, créée par `%(asctime)s`.
   - `WARNING` — le niveau, créé par `%(levelname)s`.
   - `Wrong password for avi` — le message, créé par `%(message)s`.
2. Quand quelque chose se passe sur le serveur à trois heures du matin, l'heure permet de reconstituer **ce qui s'est passé et dans quel ordre**, et de le recouper avec d'autres événements.
{{< /solution >}}

### Exercice 5: print ou logging ?

Pour chaque cas — print ou logging ? Expliquez.

1. Un programme qui demande son nom à l'utilisateur et le salue.
2. Un serveur qui tourne toute la nuit, et le matin vous voulez savoir quelles commandes ont échoué.
3. Une vérification temporaire de la valeur d'une variable pendant le développement, que vous voulez désactiver sans la supprimer.

{{< solution >}}
1. **print** — c'est la sortie que le programme montre **à l'utilisateur**.
2. **logging** (ERROR) — une information **pour le développeur**, avec heure et niveau, que l'on peut enregistrer et rechercher.
3. **logging** (DEBUG) — on peut désactiver tous les messages DEBUG en une ligne (en relevant le niveau minimum), sans supprimer de code.

La règle : **print pour l'utilisateur, logging pour le développeur.**
{{< /solution >}}

<!-- exercises:end -->

## Résumé

- **Journal** = un enregistrement d'événements : quand, quelle gravité, et ce qui s'est passé. La « boîte noire » du logiciel.
- `print` = la sortie pour l'utilisateur. `logging` = un enregistrement pour les développeurs : heure, niveau, filtrage et destinations.
- 5 niveaux : **DEBUG < INFO < WARNING < ERROR < CRITICAL**. Seuls les messages du niveau minimum et au-dessus sont affichés.
- La valeur par défaut est **WARNING**. `basicConfig` est appelé **une seule fois, en premier**.
- `logging.info("x=%s", x)` est la forme recommandée. Et ne journalisez jamais de mots de passe.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Documentation officielle

- [Logging HOWTO — When to use logging](https://docs.python.org/3/howto/logging.html#when-to-use-logging)
- [logging — Logging Levels](https://docs.python.org/3/library/logging.html#logging-levels)

<!-- docs:end -->
