Votre application plante en production à 3 h du matin. L'alerte dit "erreur 500", mais pas pourquoi. Vous ouvrez les logs, et vous tombez sur des milliers de lignes en texte libre, sans structure, mélangées entre 12 services. Trouver la cause suppose de deviner le bon motif de recherche, service par service, sur des messages dont le format change d'une application à l'autre. Les logs sont le signal d'observabilité le plus riche (chaque événement peut contenir un contexte complet) mais aussi le plus coûteux en stockage et le plus difficile à exploiter s'ils ne sont pas structurés.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Qu'est-ce qu'un log : événement discret vs métrique agrégée
- Structuré vs non structuré : pourquoi le JSON change tout
- Niveaux de sévérité : DEBUG, INFO, WARN, ERROR, FATAL, quand utiliser chacun
- Les champs essentiels : les 6 champs qu'un log structuré doit contenir
- Ce qu'il ne faut pas logger : PII, secrets, données sensibles
- Volume et coûts : pourquoi les logs sont le signal le plus cher
- Le pipeline de logs : de l'émission au requêtage
Qu'est-ce qu'un log
Section intitulée « Qu'est-ce qu'un log »Un log (ou journal) est un événement discret et horodaté : il décrit quelque chose qui s'est passé à un instant précis. Chaque requête HTTP traitée, chaque erreur rencontrée, chaque connexion à la base de données peut générer une ligne de log.
Contrairement à une métrique qui agrège des valeurs dans le temps (ex. : "142 requêtes/seconde"), un log conserve le détail individuel de chaque événement. Cela le rend extrêmement riche pour le diagnostic, mais aussi beaucoup plus volumineux à stocker.
Événement discret vs signal continu
Section intitulée « Événement discret vs signal continu »La colonne à regarder en premier est le volume : c'est elle qui explique pourquoi on conserve les métriques plusieurs mois et les logs quelques semaines. Les deux signaux ne s'opposent pas, ils répondent à des questions différentes. Une métrique indique qu'un taux d'erreur est passé de 0,1 % à 4 % entre 14 h 30 et 14 h 35 ; un log indique quelle requête a échoué, pour quel utilisateur et avec quel message. Un diagnostic part presque toujours de la métrique pour situer la fenêtre de temps, puis descend dans les logs sur cette fenêtre uniquement.
| Caractéristique | Log | Métrique |
|---|---|---|
| Nature | Événement discret | Valeur numérique agrégée |
| Granularité | Chaque occurrence individuelle | Agrégation sur une fenêtre de temps |
| Volume | Élevé (1 ligne par événement) | Faible (1 point par intervalle) |
| Coût de stockage | Élevé | Faible |
| Requêtage | Recherche textuelle / filtrage par champs | Calculs mathématiques (rate, sum, avg) |
| Usage principal | Diagnostic, audit, debug | Alerting, tendances, dashboards |
Logs structurés vs non structurés
Section intitulée « Logs structurés vs non structurés »La différence entre un log utile et un log inutilisable tient souvent à un seul choix : structuré ou non.
Le log non structuré
Section intitulée « Le log non structuré »C'est une ligne de texte libre, lisible par un humain mais difficile à exploiter par une machine :
2026-02-07 14:32:15 ERROR PaymentService - Payment failed for user 12345: insufficient funds (amount=99.99, balance=42.50)Pour extraire l'identifiant utilisateur, le montant ou le type d'erreur, il faut écrire une expression régulière, fragile, différente pour chaque format, et qui casse dès que le message change.
Le log structuré (JSON)
Section intitulée « Le log structuré (JSON) »Le même événement en JSON :
{ "timestamp": "2026-02-07T14:32:15.042Z", "level": "ERROR", "service": "payment-api", "message": "Payment failed: insufficient funds", "user_id": "12345", "amount": 99.99, "balance": 42.50, "trace_id": "abc123def456", "span_id": "789ghi"}Chaque champ est un clé/valeur typé. Un outil de requêtage peut
filtrer instantanément par level="ERROR", service="payment-api" ou
user_id="12345", sans regex, sans ambiguïté.
Comparaison
Section intitulée « Comparaison »Ce tableau sert d'argumentaire quand une équipe hésite à reprendre son logging
existant. La ligne décisive est Évolution du format : un parseur regex
casse dès qu'un développeur insère une information au milieu d'un message,
alors qu'un champ JSON supplémentaire est simplement ignoré par les requêtes
déjà écrites. La lisibilité humaine brute est le seul critère où le texte
libre garde l'avantage, et cet avantage disparaît dès qu'on passe la sortie
dans jq ou qu'on active le rendu console de la bibliothèque de logging en
développement.
| Critère | Non structuré | Structuré (JSON) |
|---|---|---|
| Lisibilité humaine brute | Bonne | Correcte (avec jq) |
| Filtrage par champ | Regex fragile | Natif (clé/valeur) |
| Corrélation avec les traces | Manuelle | Automatique via trace_id |
| Indexation | Plein texte uniquement | Par champ, rapide et ciblé |
| Évolution du format | Casse les parseurs | Ajout de champs sans rupture |
Les niveaux de sévérité
Section intitulée « Les niveaux de sévérité »Les niveaux de log (ou severity levels) indiquent l'importance d'un événement. La RFC 5424 (Syslog) en définit 8, mais la plupart des applications utilisent une échelle simplifiée à 5 niveaux.
Les 5 niveaux courants
Section intitulée « Les 5 niveaux courants »Lisez la colonne Quand l'utiliser avant les exemples : c'est elle qui porte le critère de décision, l'exemple ne fait qu'illustrer. Le piège se situe presque toujours sur la frontière entre WARN et ERROR. Beaucoup d'équipes classent en ERROR tout ce qui remonte une exception dans le code, y compris un refus métier parfaitement normal comme un mot de passe invalide ou un panier vide. Le tableau de bord d'astreinte affiche alors des centaines d'ERROR par heure qui n'appellent aucune action, et la panne réelle passe inaperçue au milieu.
| Niveau | Quand l'utiliser | Exemple |
|---|---|---|
| DEBUG | Détails fins utiles uniquement en développement ou diagnostic ponctuel | "Requête SQL exécutée : SELECT * FROM users WHERE id=12345, durée 3 ms" |
| INFO | Événements normaux qui marquent le bon fonctionnement | "Serveur démarré sur le port 8080", "Paiement de 99,99 € traité avec succès" |
| WARN | Situation anormale mais non bloquante, le service continue | "Pool de connexions à 85 % de capacité", "Retry n°2 vers le service externe" |
| ERROR | Échec d'une opération, une requête ou une action a échoué | "Paiement refusé : fonds insuffisants", "Timeout de connexion à la base" |
| FATAL | Erreur irrécupérable, le processus va s'arrêter | "Impossible de lire le fichier de configuration", "Port 8080 déjà occupé" |
Choisir le bon niveau
Section intitulée « Choisir le bon niveau »Le choix du niveau est un acte d'engagement envers l'équipe d'astreinte. Un WARN mal calibré génère du bruit ; un ERROR manquant masque un problème réel.
Deux questions pour choisir :
- Quelqu'un doit-il agir immédiatement ? → ERROR ou FATAL
- Est-ce que ça fonctionne encore ? → Si oui, WARN. Si non, ERROR.
La correspondance syslog (RFC 5424)
Section intitulée « La correspondance syslog (RFC 5424) »Pour les systèmes qui utilisent syslog, voici la correspondance avec les 8 niveaux de la RFC 5424 :
| Code | Niveau RFC 5424 | Correspondance application |
|---|---|---|
| 0 | Emergency | FATAL (système inutilisable) |
| 1 | Alert | FATAL (action immédiate requise) |
| 2 | Critical | FATAL / ERROR critique |
| 3 | Error | ERROR |
| 4 | Warning | WARN |
| 5 | Notice | INFO (événement significatif) |
| 6 | Informational | INFO |
| 7 | Debug | DEBUG |
Retenez que la sévérité syslog est un chiffre qui croît vers le moins
grave : 0 désigne le plus critique, 7 le plus bavard. C'est l'inverse de
l'intuition, et c'est la source d'erreurs de filtrage classiques dans
rsyslog ou journalctl. Ainsi, journalctl -p 3 ne montre pas les messages
de niveau 3 seulement, mais tous ceux dont la priorité est inférieure ou
égale à 3, donc Error, Critical, Alert et Emergency.
Les champs essentiels d'un log structuré
Section intitulée « Les champs essentiels d'un log structuré »Un log structuré utile contient au minimum 6 champs qui répondent aux questions fondamentales du diagnostic : quand, quoi, où, qui, et comment le relier aux autres signaux.
| Champ | Pourquoi il est indispensable | Exemple |
|---|---|---|
timestamp | Quand, ordonne les événements chronologiquement | "2026-02-07T14:32:15.042Z" (ISO 8601, UTC) |
level | Gravité, permet le filtrage par sévérité | "ERROR" |
service | Où, identifie le composant émetteur | "payment-api" |
message | Quoi, décrit l'événement en langage humain | "Payment failed: insufficient funds" |
trace_id | Corrélation inter-services, relie le log à une trace distribuée (tracing) | "abc123def456" |
request_id | Corrélation locale, regroupe les logs d'une même requête, utile même sans tracing | "req-789ghi" |
trace_id est propagé entre les services par le système de tracing
distribué (OpenTelemetry, Jaeger…). request_id est généré à l'entrée
(reverse-proxy ou API gateway) et transmis via un header (X-Request-ID) :
il permet de corréler les logs d'une requête même si le tracing distribué
n'est pas encore en place.
Le message : court et stable
Section intitulée « Le message : court et stable »Le champ message doit rester lisible par un humain et stable dans
le temps. Les détails variables (montant, identifiant, durée) vont
dans des champs dédiés (error_type, duration_ms, amount…), pas
dans le message lui-même.
- ✅
"Payment failed: insufficient funds"+ champamount: 99.99 - ❌
"Payment of 99.99 EUR failed for user 12345 after 127ms: insufficient funds"
Un message stable permet de grouper les logs par type d'événement. Si chaque message est unique (parce qu'il contient des valeurs), le groupement devient impossible.
Champs complémentaires recommandés
Section intitulée « Champs complémentaires recommandés »Aucun de ces champs n'est obligatoire, mais chacun répond à une question qui
revient systématiquement pendant un incident. version et hostname
permettent de trancher entre « tout le parc est touché » et « une seule
instance dérive », ce qui oriente vers un rollback ou vers le
remplacement d'un pod. duration_ms rend les logs exploitables pour
analyser la latence sans avoir à instrumenter une métrique dédiée.
user_id est le plus délicat des sept : il rapproche le log d'une donnée
personnelle et impose donc une pseudonymisation et une durée de rétention
explicites.
| Champ | Usage |
|---|---|
span_id | Situe le log dans un span précis de la trace |
environment | Distingue production / staging / dev |
version | Identifie la version de l'application déployée |
hostname / pod_name | Localise l'instance émettrice |
user_id | Identifie l'utilisateur concerné (attention PII) |
duration_ms | Mesure la durée de l'opération loguée |
error_type | Catégorise l'erreur (TimeoutError, ValidationError) |
Exemple complet
Section intitulée « Exemple complet »{ "timestamp": "2026-02-07T14:32:15.042Z", "level": "ERROR", "service": "payment-api", "version": "2.3.1", "environment": "production", "hostname": "payment-api-7b4d9-xk2p", "message": "Payment failed: insufficient funds", "trace_id": "abc123def456", "span_id": "789ghi", "request_id": "req-a1b2c3", "user_id": "usr_12345", "amount": 99.99, "currency": "EUR", "error_type": "InsufficientFundsError", "duration_ms": 127}Ce log permet en un coup d'oeil de répondre à : quand (timestamp), quoi (message + error_type), où (service + hostname), pour qui (user_id), et de naviguer vers la trace complète (trace_id).
Ce qu'il ne faut pas logger
Section intitulée « Ce qu'il ne faut pas logger »Les logs traversent souvent des pipelines de collecte, des stockages partagés et des dashboards accessibles à plusieurs équipes. Certaines données ne doivent jamais s'y trouver.
Données interdites dans les logs
Section intitulée « Données interdites dans les logs »La colonne Risque décrit ce qui se produit une fois la donnée écrite, et c'est le point à garder en tête : un log est répliqué, indexé et sauvegardé dans la minute qui suit son émission. Le retirer après coup impose de purger l'index du backend, les instantanés de sauvegarde et les éventuels exports vers un SIEM, sans certitude d'avoir tout couvert. En pratique, un secret parti dans les logs est un secret à révoquer, pas à effacer.
| Catégorie | Exemples | Risque |
|---|---|---|
| Secrets | Mots de passe, tokens API, clés privées | Compromission du système |
| Headers d'authentification | Authorization, Cookie, Set-Cookie | Tokens/sessions exposés en clair |
| PII (données personnelles) | E-mail, adresse postale, numéro de téléphone, IP | Non-conformité RGPD/CNIL |
| Données financières | Numéro de carte bancaire, IBAN complet | Violation PCI-DSS |
| Données médicales | Diagnostics, prescriptions | Violation réglementaire |
Bonnes pratiques
Section intitulée « Bonnes pratiques »Ces règles s'appliquent à deux endroits complémentaires, et les deux se combinent. Le masquage à l'émission est le plus fiable : la valeur sensible ne quitte jamais le processus applicatif, mais chaque service doit être modifié. Le filtrage dans le collecteur rattrape ce que les applications ont laissé passer et se déploie en une seule fois, au prix d'un traitement supplémentaire appliqué à tout le flux. Une équipe qui ne peut pas reprendre 30 services d'un coup commence par le collecteur, puis remonte le masquage dans le code service par service.
- Masquer les données sensibles avant l'émission (ex. :
"card": "****1234") - Ne pas logger les corps de requêtes HTTP complets en production (ils contiennent souvent des formulaires avec des données utilisateur)
- Identifier les champs PII et les exclure du pipeline de collecte
ou les anonymiser dans le collecteur (ex. : processeur OTel
transform) - En cas de doute, ne pas logger le champ, on peut toujours l'ajouter plus tard
Volume et coûts : le signal le plus cher
Section intitulée « Volume et coûts : le signal le plus cher »Les logs sont, de loin, le signal d'observabilité le plus volumineux et le plus coûteux. En ordre de grandeur, une application web génère entre 1 et 10 Go de logs par jour (davantage sous fort trafic ou avec des logs verbeux). En comparaison, ses métriques Prometheus occupent quelques centaines de Mo, et ses traces (échantillonnées) quelques Go par semaine. Ces chiffres varient fortement selon le trafic, le nombre de services et le niveau de verbosité.
Pourquoi les logs coûtent cher
Section intitulée « Pourquoi les logs coûtent cher »Le coût des logs est le produit de trois facteurs :
Volume × Rétention × Requêtage = Coût
| Facteur | Ce qui l'augmente | Levier d'optimisation |
|---|---|---|
| Volume | Logs DEBUG en production, corps de requêtes, logs verbeux | Réduire le niveau en prod (INFO minimum), filtrer à la source |
| Rétention | Conservation illimitée ("on ne sait jamais") | Définir des politiques par importance : 7 j DEBUG, 30 j INFO/WARN, 90 j ERROR |
| Requêtage | Index plein texte sur tous les champs | Indexer uniquement les champs utiles (labels dans Loki) |
Comparaison du coût par signal
Section intitulée « Comparaison du coût par signal »Ces ordres de grandeur ne servent pas à établir un budget, ils servent à arbitrer. À trafic comparable, ce sont les logs qui pèsent le plus lourd, parce qu'ils sont les seuls à conserver chaque occurrence sans agrégation. La colonne qui pilote réellement la facture est la rétention : ramener les logs de 90 à 30 jours divise le stockage par trois sans toucher à une ligne de code applicatif, alors que réduire le volume émis oblige à reprendre l'instrumentation service par service.
| Signal | Volume typique/jour | Coût relatif de stockage | Rétention courante |
|---|---|---|---|
| Métriques | ~100 Mo – 1 Go | Faible | 15 – 90 jours |
| Traces (échantillonnées) | ~500 Mo – 5 Go | Moyen | 7 – 30 jours |
| Logs | ~1 – 50 Go | Élevé | 7 – 90 jours |
Le pipeline de logs
Section intitulée « Le pipeline de logs »Les logs parcourent un chemin en 4 étapes entre leur émission et le moment où un humain les consulte. Comprendre ce pipeline est essentiel pour savoir où agir sur le volume, le format et l'enrichissement.
-
Émission
L'application génère le log via une bibliothèque de logging (Python
structlog, JavaLogback, Goslog, Node.jspino). C'est à cette étape que le format (structuré ou non) et le niveau de sévérité sont décidés. En environnement conteneurisé, les logs sont émis sur stdout/stderr, le runtime (Docker, Kubernetes) les capture automatiquement. -
Collecte
Un agent ou un collecteur récupère les logs depuis la source (fichier, stdout, socket syslog) et les transmet au stockage. Exemples : Promtail (pour Loki), Fluentd, Fluent Bit, OpenTelemetry Collector. C'est à cette étape qu'on peut filtrer, enrichir (ajouter des labels Kubernetes), transformer le format, et échantillonner ou limiter le débit (rate limit) des logs très fréquents pour maîtriser le volume.
-
Stockage et indexation
Le backend stocke les logs et les indexe pour permettre le requêtage. Deux approches existent : Elasticsearch indexe le contenu riche des logs (recherche plein texte puissante, mais coûteuse en stockage et en ressources). Loki optimise l'index sur des labels et s'appuie sur le scan du contenu au moment du requêtage, plus économique, mais les recherches dans le corps du message sont plus lentes sur de gros volumes.
-
Requêtage et visualisation
L'opérateur recherche dans les logs via un langage de requête (LogQL pour Loki, KQL pour Elasticsearch/Kibana) depuis une interface comme Grafana Explore. C'est à cette étape que la structure du log paie : un log JSON avec des champs nommés se filtre en secondes ; un log texte libre nécessite des regex lentes.
Schéma du pipeline
Section intitulée « Schéma du pipeline »Le chemin que prend un log dépend de l'environnement d'exécution :
- En conteneurs (Docker, Kubernetes) : l'application écrit sur stdout/stderr, le runtime capture les logs et un agent (Promtail, Fluent Bit, OTel Collector) les collecte
- Sur VM / bare-metal : l'application écrit dans un fichier (.log), un agent le lit (tail) et le transmet
- Syslog : l'application envoie sur une socket (UDP/TCP), rsyslog ou syslog-ng reçoit et route
Ce qui distingue vraiment ces trois variantes, c'est l'endroit où se fait la
bufferisation, donc l'endroit où les lignes se perdent quand ça sature. En
conteneurs, si personne ne consomme stdout assez vite, c'est le runtime qui
retient les lignes et finit par en perdre au moment d'une rotation de fichier.
Sur VM, le fichier absorbe les à-coups mais expose au disque plein si
logrotate n'est pas configuré. En syslog UDP, il n'y a aucun accusé de
réception : un collecteur saturé perd les messages en silence, ce qui rend le
transport TCP obligatoire dès que les logs servent de piste d'audit.
Checklist : log prêt pour la production
Section intitulée « Checklist : log prêt pour la production »Cette liste se relit au moment de la revue de code, pas après la mise en production : chaque point qui manque coûte beaucoup plus cher à rattraper une fois que les logs sont émis en volume. Les deux premiers points sont ceux qui bloquent l'ingestion elle-même, un log multi-lignes ou un timestamp sans fuseau produit des entrées mal découpées ou mal ordonnées dans le backend. Les suivants concernent l'exploitabilité et la conformité.
- 1 événement = 1 log JSON, pas de logs multi-lignes (stack traces
incluses dans un champ
error.stack, pas étalées sur 30 lignes) - Timestamp UTC ISO 8601,
2026-02-07T14:32:15.042Z, jamais l'heure locale levelcalibré, pas de WARN partout, pas d'ERROR pour un cas nominalservice,version,environmentrenseignés, identifient précisément l'émetteurtrace_idet/ourequest_idprésents, permettent la corrélation- Aucun secret ni PII en clair, ni dans le message, ni dans les champs
- Pas de payloads énormes, pas de body HTTP complets, pas de dumps d'objets
- Message court et stable, les variables dans des champs dédiés
Pièges courants
Section intitulée « Pièges courants »Ces six pièges se corrigent dans l'ordre du tableau, parce qu'ils dépendent les
uns des autres. Le passage au JSON structuré conditionne tout le reste :
sans champs nommés, ni le trace_id, ni la convention de nommage, ni le
filtrage à la collecte n'ont de prise. La dernière ligne est celle qui coûte le
plus cher à corriger tardivement : compter des occurrences par recherche
textuelle au lieu d'exposer un compteur applicatif revient à payer le
stockage de millions de lignes pour obtenir un seul nombre, que la métrique
aurait fourni pour quelques octets par intervalle.
| Piège | Pourquoi c'est un problème | Que faire |
|---|---|---|
| Logger en texte libre | Impossible à filtrer proprement, regex fragiles | Adopter le JSON structuré avec des champs nommés |
Oublier le trace_id | Les logs sont isolés, pas de corrélation avec les traces | Injecter le trace_id via le contexte OpenTelemetry ou un middleware |
| Logger les corps de requêtes | Volume énorme + risque PII | Logger uniquement les métadonnées (méthode, path, status, durée) |
| Pas de convention de nommage | userId / user_id / UserID dans 3 services différents | Définir un schéma de nommage d'équipe (snake_case recommandé) |
| Ignorer la rétention | Les logs s'accumulent indéfiniment → coût explosif | Définir une politique de rétention par niveau |
| Confondre log et métrique | "Compter les erreurs via grep" est lent et imprécis | Les compteurs appartiennent aux métriques, pas aux logs |
À retenir
Section intitulée « À retenir »-
Un log est un événement discret et horodaté, il capture le détail individuel, contrairement à la métrique qui agrège.
-
Un log structuré (JSON avec des champs nommés) est interrogeable par une machine ; un log texte libre ne l'est que par
grep. -
Les 6 champs essentiels sont :
timestamp(UTC, ISO 8601),level,service,message,trace_id,request_id. -
Les niveaux de sévérité (DEBUG → INFO → WARN → ERROR → FATAL) sont un engagement envers l'équipe d'astreinte : un ERROR doit toujours mériter une investigation.
-
Ne jamais logger de secrets, de PII en clair ni de données financières complètes.
-
Les logs sont le signal le plus cher : le volume en production doit être maîtrisé (pas de DEBUG permanent, rétention adaptée).
-
Le pipeline émission → collecte → stockage → requêtage est la chaîne à optimiser. La qualité du log se décide à l'émission, pas au requêtage.
-
IETF, RFC 5424 : The Syslog Protocol : datatracker.ietf.org/doc/html/rfc5424, la spécification des niveaux de sévérité syslog (0–7) et du format structuré syslog.
-
OpenTelemetry, Logs Data Model : opentelemetry.io/docs/specs/otel/logs/data-model, le modèle de données OTel pour les logs, incluant severity, body, attributes et les liens avec traces/métriques.
-
Uptrace, Structured Logging: Best Practices & JSON Examples : uptrace.dev/glossary/structured-logging, guide pratique des champs JSON, des bibliothèques par langage et de la corrélation avec les traces.