Aller au contenu
medium

Les logs : comprendre le signal le plus riche (et le plus cher)

26 min de lecture

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.

  • 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

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.

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éristiqueLogMétrique
NatureÉvénement discretValeur numérique agrégée
GranularitéChaque occurrence individuelleAgré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êtageRecherche textuelle / filtrage par champsCalculs mathématiques (rate, sum, avg)
Usage principalDiagnostic, audit, debugAlerting, tendances, dashboards

La différence entre un log utile et un log inutilisable tient souvent à un seul choix : structuré ou non.

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 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é.

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èreNon structuréStructuré (JSON)
Lisibilité humaine bruteBonneCorrecte (avec jq)
Filtrage par champRegex fragileNatif (clé/valeur)
Corrélation avec les tracesManuelleAutomatique via trace_id
IndexationPlein texte uniquementPar champ, rapide et ciblé
Évolution du formatCasse les parseursAjout de champs sans rupture

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.

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.

NiveauQuand l'utiliserExemple
DEBUGDé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"
WARNSituation 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"
FATALErreur irrécupérable, le processus va s'arrêter"Impossible de lire le fichier de configuration", "Port 8080 déjà occupé"

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 :

  1. Quelqu'un doit-il agir immédiatement ? → ERROR ou FATAL
  2. Est-ce que ça fonctionne encore ? → Si oui, WARN. Si non, ERROR.

Pour les systèmes qui utilisent syslog, voici la correspondance avec les 8 niveaux de la RFC 5424 :

CodeNiveau RFC 5424Correspondance application
0EmergencyFATAL (système inutilisable)
1AlertFATAL (action immédiate requise)
2CriticalFATAL / ERROR critique
3ErrorERROR
4WarningWARN
5NoticeINFO (événement significatif)
6InformationalINFO
7DebugDEBUG

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.

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.

ChampPourquoi il est indispensableExemple
timestampQuand, ordonne les événements chronologiquement"2026-02-07T14:32:15.042Z" (ISO 8601, UTC)
levelGravité, permet le filtrage par sévérité"ERROR"
service, identifie le composant émetteur"payment-api"
messageQuoi, décrit l'événement en langage humain"Payment failed: insufficient funds"
trace_idCorrélation inter-services, relie le log à une trace distribuée (tracing)"abc123def456"
request_idCorré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 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" + champ amount: 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.

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.

ChampUsage
span_idSitue le log dans un span précis de la trace
environmentDistingue production / staging / dev
versionIdentifie la version de l'application déployée
hostname / pod_nameLocalise l'instance émettrice
user_idIdentifie l'utilisateur concerné (attention PII)
duration_msMesure la durée de l'opération loguée
error_typeCatégorise l'erreur (TimeoutError, ValidationError)
{
"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).

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.

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égorieExemplesRisque
SecretsMots de passe, tokens API, clés privéesCompromission du système
Headers d'authentificationAuthorization, Cookie, Set-CookieTokens/sessions exposés en clair
PII (données personnelles)E-mail, adresse postale, numéro de téléphone, IPNon-conformité RGPD/CNIL
Données financièresNuméro de carte bancaire, IBAN completViolation PCI-DSS
Données médicalesDiagnostics, prescriptionsViolation réglementaire

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

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é.

Le coût des logs est le produit de trois facteurs :

Volume × Rétention × Requêtage = Coût

FacteurCe qui l'augmenteLevier d'optimisation
VolumeLogs DEBUG en production, corps de requêtes, logs verbeuxRéduire le niveau en prod (INFO minimum), filtrer à la source
RétentionConservation illimitée ("on ne sait jamais")Définir des politiques par importance : 7 j DEBUG, 30 j INFO/WARN, 90 j ERROR
RequêtageIndex plein texte sur tous les champsIndexer uniquement les champs utiles (labels dans Loki)

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.

SignalVolume typique/jourCoût relatif de stockageRétention courante
Métriques~100 Mo – 1 GoFaible15 – 90 jours
Traces (échantillonnées)~500 Mo – 5 GoMoyen7 – 30 jours
Logs~1 – 50 GoÉlevé7 – 90 jours

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.

  1. Émission

    L'application génère le log via une bibliothèque de logging (Python structlog, Java Logback, Go slog, Node.js pino). 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.

  2. 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.

  3. 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.

  4. 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.

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.

Pipeline de collecte de logs : Application vers Collecteur vers Backend vers Interface

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
  • level calibré, pas de WARN partout, pas d'ERROR pour un cas nominal
  • service, version, environment renseignés, identifient précisément l'émetteur
  • trace_id et/ou request_id pré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

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ègePourquoi c'est un problèmeQue faire
Logger en texte libreImpossible à filtrer proprement, regex fragilesAdopter le JSON structuré avec des champs nommés
Oublier le trace_idLes logs sont isolés, pas de corrélation avec les tracesInjecter le trace_id via le contexte OpenTelemetry ou un middleware
Logger les corps de requêtesVolume énorme + risque PIILogger uniquement les métadonnées (méthode, path, status, durée)
Pas de convention de nommageuserId / user_id / UserID dans 3 services différentsDéfinir un schéma de nommage d'équipe (snake_case recommandé)
Ignorer la rétentionLes logs s'accumulent indéfiniment → coût explosifDéfinir une politique de rétention par niveau
Confondre log et métrique"Compter les erreurs via grep" est lent et imprécisLes compteurs appartiennent aux métriques, pas aux logs
  • 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.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn