---
title: "Logging structuré avec un format de log et des niveaux que vous cherchez par champ"
description: "Le logging structuré met chaque valeur dans son propre champ plutôt que dans une phrase. Nous avons envoyé 1 commande de 3 façons à LogBrew et montrons laquelle vous retrouvez par order_id."
canonical: "https://logbrew.co/fr/guides/structured-logging"
last_updated: "2026-11-10"
---

# Logging structuré avec un format de log et des niveaux que vous cherchez par champ

Le logging structuré met chaque valeur dans son propre champ plutôt que dans une phrase. Nous avons envoyé 1 commande de 3 façons à LogBrew et montrons laquelle vous retrouvez par `order_id`.

Testé le 10 novembre 2026 avec Python 3.13.5 et logbrew-sdk 0.1.29

Par [Furkan Erday](https://github.com/furkanerday). Furkan Erday est le fondateur de LogBrew. Il développe LogBrew avec Claude Code et Codex tous les jours.

Canonical: https://logbrew.co/fr/guides/structured-logging

## Quelle ligne de log vous retrouvez par `order_id`

| Où est passé l’id | Recherche texte | Filtre par champ |
| --- | --- | --- |
| Dans le texte du message | Trouvée | Non trouvée |
| Dans les champs `extra={}` | Non trouvée | Trouvée |
| Dans un dict à l’intérieur de `extra={}` | Non trouvée | Non trouvée |

Nous avons envoyé 1 commande de 3 façons via le handler Python, puis nous l’avons cherchée. Une chope pleine signifie que la recherche a trouvé la ligne.

## Choisir un format de log que vous pouvez filtrer

### Garder le message fixe, mettre les valeurs dans des champs

Un champ garde son nom et son type. Une valeur dans le texte du message n’est qu’une sous-chaîne. En Python, passez les champs dans `extra={}`, et le message reste le même pour chaque commande.

```bash
log.info("order paid", extra={"order_id": "ord_42", "amount_cents": 1999})
```

### Écrire des lignes JSON quand un programme lit le log

Une sous-classe de `Formatter` transforme chaque record en 1 objet JSON par ligne, avec les champs standard et chaque valeur `extra`. Un collecteur de logs parse la ligne sans regex.

```bash
import json
import logging

RECORD_FIELDS = set(vars(logging.LogRecord("", 0, "", 0, "", (), None))) | {"message", "asctime"}


class JsonFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        line = {
            "time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        line.update({k: v for k, v in vars(record).items() if k not in RECORD_FIELDS})
        if record.exc_info:
            line["exception"] = self.formatException(record.exc_info)
        return json.dumps(line)


handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[handler])
log = logging.getLogger("taproom")

log.info("order paid", extra={"order_id": "ord_42", "amount_cents": 1999})
log.warning("keg below 10%", extra={"keg": "ipa", "percent": 8})
```

Sortie

```text
{"time": "2026-11-10T08:34:04+0300", "level": "INFO", "logger": "taproom", "message": "order paid", "order_id": "ord_42", "amount_cents": 1999}
{"time": "2026-11-10T08:34:04+0300", "level": "WARNING", "logger": "taproom", "message": "keg below 10%", "keg": "ipa", "percent": 8}
```

### Envoyer les champs à LogBrew

`LogBrewLoggingHandler` envoie chaque record avec ses valeurs `extra` en champs. Nous avons envoyé la même commande de 3 façons, dans le texte du message, en champs `extra` et dans un dict à l’intérieur de `extra`.

```bash
import logging
import os

from logbrew_sdk import HttpTransport, LogBrewClient, LogBrewLoggingHandler

client = LogBrewClient.create(api_key=os.environ["LOGBREW_SERVER_API_KEY"])
transport = HttpTransport()
log = logging.getLogger("taproom")
log.addHandler(LogBrewLoggingHandler(client, transport, metadata={"service": "taproom"}))
log.setLevel(logging.INFO)

logging.getLogger("taproom.text").info("order %s paid, %d cents", "ord_42", 1999)
logging.getLogger("taproom.fields").info(
    "order paid", extra={"order_id": "ord_42", "amount_cents": 1999}
)
logging.getLogger("taproom.nested").info(
    "order paid", extra={"order": {"id": "ord_42", "amount_cents": 1999}}
)

response = client.flush(transport)
print("HTTP", response.status_code, "accepted", response.accepted_events, "logs")
```

Sortie

```text
HTTP 200 accepted 3 logs
```

### Retrouver la ligne par champ

`GET /api/logs` accepte `attribute_filters` avec un chemin de champ, un type, un opérateur et une valeur. Le filtre sur `order_id` n’a trouvé que la ligne envoyée avec `extra`. Le dict à l’intérieur de `extra` a été ignoré, parce que le handler ne garde que les chaînes, les nombres, les booléens et `None`.

```bash
curl -sS -G "https://api.logbrew.co/api/logs" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$LOGBREW_PROJECT" \
  --data-urlencode "since=2026-11-10T05:31:33Z" \
  --data-urlencode 'attribute_filters=[{"path":"/metadata/order_id","type":"string","operator":"eq","value":"ord_42"}]' \
  | jq -c '.[] | {message, order_id: .attributes.metadata.order_id}'
```

Sortie

```text
{"message":"order paid","order_id":"ord_42"}
```

## Utiliser les niveaux de log de la même façon dans chaque service

### Utiliser 4 niveaux en production

`info` pour ce qui s’est passé, `warning` pour ce qui a mal tourné mais a été géré, `error` pour une requête ou un job qui a échoué, et `critical` pour ce qui arrête le service. Gardez `debug` pour les exécutions locales. Le handler LogBrew envoie les records DEBUG en `info`.

### Fixer le niveau une fois, au démarrage

Lisez le niveau depuis l’environnement, pour qu’1 hôte puisse tourner en DEBUG pendant que les autres restent en INFO. En Python, `logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))` s’en charge.

### Logger les nombres comme des nombres

`amount_cents: 1999` garde son type, donc le filtre `amount_cents >= 1000` le trouve. Nous avons aussi envoyé `amount_cents: "1999"` en texte, et le filtre numérique a sauté cette ligne.

```bash
curl -sS -G "https://api.logbrew.co/api/logs" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$LOGBREW_PROJECT" \
  --data-urlencode "since=2026-11-10T05:31:33Z" \
  --data-urlencode 'attribute_filters=[{"path":"/metadata/amount_cents","type":"number","operator":"gte","value":1000}]' \
  | jq -c '.[] | {message, amount_cents: .attributes.metadata.amount_cents}'
```

Sortie

```text
{"message":"order paid","amount_cents":1999}
```

## Ce que nous avons constaté en testant

Nous avons envoyé 1 commande de 3 façons via le handler Python, puis nous l’avons cherchée avec l’API LogBrew. La recherche texte `search=ord_42` a trouvé 1 des 3 lignes, celle avec l’id dans le message.

Le filtre `order_id = ord_42` a trouvé 1 ligne, celle envoyée avec `extra={"order_id": "ord_42"}`. Le filtre `amount_cents >= 1000` a trouvé la même ligne, parce que le handler a gardé 1999 comme nombre.

La ligne envoyée avec `extra={"order": {...}}` n’a rien donné. Le handler envoie les chaînes, les nombres, les booléens et `None` de `extra` et saute un dict ou une liste.

```bash
curl -sS -G "https://api.logbrew.co/api/logs" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$LOGBREW_PROJECT" \
  --data-urlencode "since=2026-11-10T05:31:33Z" \
  --data-urlencode "search=ord_42" \
  | jq -c '.[] | {message, order_id: .attributes.metadata.order_id}'
```

Sortie

```text
{"message":"order ord_42 paid, 1999 cents","order_id":null}
```

## Texte brut ou JSON comme format de log

Un format `%(message)s` brut perd chaque valeur `extra`. La ligne ci-dessous se termine par `INFO order paid` sans id de commande, donc personne ne retrouve la commande plus tard.

Une ligne JSON garde les champs et leurs types, et un collecteur de logs la parse sans regex. Une personne la lit moins facilement, donc gardez le message court et les noms de champs stables.

Python n’a pas de formatter JSON intégré. python/cpython#77691 en a demandé un en 2018 et a été fermé, parce que la documentation montre déjà comment en écrire un. Le `Formatter` de 15 lignes ci-dessus est cette recette.

```bash
import logging

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

log.info("order paid", extra={"order_id": "ord_42", "amount_cents": 1999})
```

Sortie

```text
2026-11-10 08:34:04,955 INFO order paid
```

## Ce que LogBrew enregistre

Chaque log garde son niveau, le nom du logger et chaque champ primitif de `extra`, plus les champs de `metadata={}` sur le handler. L’API filtre par chemin de champ, type et opérateur. Le texte accepte `eq`, `ne`, `contains` et `starts_with`, et les nombres acceptent `gt`, `gte`, `lt` et `lte`.

LogBrew n’indexe pas un champ dont le nom contient `password`, `secret`, `token`, `cookie` ou `authorization`, donc un filtre ne le trouve jamais. Gardez quand même les secrets hors des lignes de log.

Un log envoyé dans une trace porte les ids de trace et de span, donc un filtre par `order_id` mène à la requête qui a produit la ligne.

## Questions fréquentes

### Qu’est-ce que le logging structuré ?

Un logging où chaque valeur est un champ nommé avec un type, en JSON ou en champs sur un record de log, plutôt que des mots dans une phrase. Un outil de recherche filtre alors par champ, pas par sous-chaîne.

### Quels sont les niveaux de log, dans l’ordre ?

DEBUG, INFO, WARNING, ERROR, CRITICAL, avec les nombres 10 à 50 en Python. Un logger réglé sur INFO laisse passer INFO et au-dessus et ignore DEBUG.

### Faut-il logger en JSON ou en texte brut ?

En JSON quand un programme lit les lignes, comme un collecteur, un fichier ou un outil de recherche. En texte brut quand seule une personne lit le terminal. Gardez les mêmes champs dans les deux.

### Comment ajouter des champs à une ligne de log Python ?

Passez `extra={"order_id": "ord_42"}` à l’appel de log. Le formatter standard ignore les champs extra. Un formatter JSON ou le handler LogBrew les garde.

### Puis-je chercher des logs par champ dans LogBrew ?

Oui. `GET /api/logs` accepte jusqu’à 4 `attribute_filters`, chacun avec un chemin de champ, un type et un opérateur comme `eq` ou `gte`.

## Filtrez vos logs par n’importe quel champ

LogBrew garde chaque champ que vous loggez et retrouve la ligne par champ, à côté de la trace et du problème auxquels elle appartient. Commencez avec le forfait Free généreux, sans carte bancaire.

[Commencer gratuitement](https://logbrew.co/fr)

## Sources

- [Python docs, Logging Cookbook](https://docs.python.org/3/howto/logging-cookbook.html)
- [Python docs, LogRecord attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes)
- [python/cpython#77691](https://github.com/python/cpython/issues/77691)

## Autres guides pour les agents et Python

- [Ajouter un serveur MCP à Claude Code et Codex](https://logbrew.co/fr/guides/mcp-server-claude-code-codex)
- [Utiliser AGENTS.md dans Claude Code et Codex](https://logbrew.co/fr/guides/agents-md)
- [Corriger l’erreur Claude overloaded 529 et l’erreur rate limit 429](https://logbrew.co/fr/guides/claude-overloaded-rate-limit-error)
- [Configurer le logging Python avec les bons niveaux de log et la capture des exceptions](https://logbrew.co/fr/guides/python-logging)
- [Corriger les erreurs dans Claude Code, Codex, JavaScript et Python](https://logbrew.co/fr/guides)
