Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Les logs : structure, niveaux et coût du signal le plus riche

Par mis à jour le

Améliorer cette page

Votre application renvoie des erreurs 500 à 3 h du matin, et l'alerte ne dit pas pourquoi. Vous ouvrez les journaux et tombez sur des milliers de lignes en texte libre, mélangées entre douze services, dont le format change d'une application à l'autre. Les logs sont le signal d'observabilité le plus riche, puisque chaque événement peut porter tout son contexte, mais aussi le plus coûteux à stocker et le plus pénible à exploiter quand ils ne sont pas structurés.

Cette leçon s'adresse à qui a déjà centralisé des journaux, par exemple dans le lab Loki, et veut savoir ce qui rend un log utile quel que soit l'outil qui le stocke : le format, le niveau de sévérité, les champs indispensables, ce qu'il ne faut jamais écrire et ce qui fait grimper la facture.

  • Distinguer un log d'une métrique, et savoir quand chercher l'un ou l'autre
  • Comprendre pourquoi un log structuré en JSON se requête là où un texte libre se devine
  • Choisir le niveau de sévérité d'un événement : DEBUG, INFO, WARN, ERROR ou FATAL
  • Reconnaître les champs indispensables d'un log structuré
  • Repérer les données qui ne doivent jamais apparaître dans un log
  • Comprendre ce qui fait le coût des logs, et pourquoi le DEBUG en production le fait exploser

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 traitée, chaque erreur rencontrée, chaque connexion à une base de données peut produire une ligne. Là où une métrique agrège des valeurs dans le temps, le log conserve le détail individuel de chaque événement.

Les deux signaux 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 de cette fenêtre uniquement.

CaractéristiqueLogMétrique
NatureÉvénement discretValeur numérique agrégée
GranularitéChaque occurrenceAgrégat sur une fenêtre de temps
VolumeÉlevé : une ligne par événementFaible : un point par intervalle
Coût de stockageÉlevéFaible
RequêtageFiltrage par champs, recherche textuelleCalculs (taux, somme, percentile)
Usage principalDiagnostic, audit, débogageAlerting, tendances, tableaux de bord

La colonne du volume explique une pratique courante : on garde les métriques plusieurs mois et les logs quelques semaines. Elle explique aussi une erreur fréquente, compter des événements en fouillant les logs, alors qu'un compteur exposé en métrique donnerait le même chiffre pour quelques octets. La leçon Les métriques détaille ce signal complémentaire.

La différence entre un log utile et un log inutilisable tient souvent à un seul choix : structuré ou non. Un log non structuré est une phrase écrite pour un humain ; un log structuré est un ensemble de champs nommés qu'une machine filtre sans deviner. Les deux exemples qui suivent décrivent le même événement.

C'est une ligne de texte libre, lisible par un humain mais difficile à exploiter par une machine. Pour en extraire l'identifiant de l'utilisateur, le montant ou le type d'erreur, il faut écrire une expression régulière, différente pour chaque format, et qui casse dès que le développeur reformule son message.

2026-02-07 14:32:15 ERROR PaymentService - Payment failed for user 12345: bank provider timeout after 3000ms (amount=99.99)

Le même événement en JSON sépare chaque information dans un champ typé. Un outil de requêtage filtre alors directement sur level, service ou user_id, sans expression régulière et sans ambiguïté, et un champ ajouté demain ne casse aucune requête déjà écrite.

{
"timestamp": "2026-02-07T14:32:15.042Z",
"level": "ERROR",
"service": "payment-api",
"message": "Payment failed: bank provider timeout",
"user_id": "usr_8f3a",
"amount": 99.99,
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7"
}

Ce tableau sert d'argumentaire quand une équipe hésite à reprendre son logging existant. La ligne décisive est l'évolution du format : un parseur à base d'expressions régulières casse dès qu'une information s'insère au milieu d'un message, alors qu'un champ JSON supplémentaire est simplement ignoré. La lisibilité brute est le seul critère où le texte libre garde l'avantage, et il disparaît dès qu'on passe la sortie dans un formateur comme jq.

CritèreNon structuréStructuré (JSON)
Lisibilité humaine bruteBonneCorrecte, excellente avec un formateur
Filtrage par champExpression régulière fragileNatif (clé/valeur)
Corrélation avec les tracesManuelleDirecte, par le champ trace_id
IndexationPlein texte uniquementPar champ, rapide et ciblée
Évolution du formatCasse les parseursAjout de champs sans rupture

Le niveau de sévérité dit l'importance d'un événement, et il sert de premier filtre à tout le monde : à l'équipe d'astreinte qui cherche ce qui exige une action, au pipeline qui décide quoi garder, au stockage qui applique des durées de rétention différentes. La RFC 5424 en définit huit, mais la plupart des applications utilisent une échelle à cinq niveaux.

Lisez la colonne « Quand l'utiliser » avant les exemples : c'est elle qui porte le critère de décision. Le piège se situe presque toujours à la frontière entre WARN et ERROR : beaucoup d'équipes classent en ERROR tout ce qui lève une exception, y compris un refus métier normal comme un mot de passe invalide. Le tableau de bord d'astreinte affiche alors des centaines d'ERROR par heure qui n'appellent aucune action, et la vraie panne passe inaperçue.

NiveauQuand l'utiliserExemple
DEBUGDétails fins, utiles en développement ou en diagnostic ponctuel« Requête SQL exécutée en 3 ms »
INFOÉvénement normal qui marque le bon fonctionnement« Serveur démarré sur le port 8080 »
WARNSituation anormale mais non bloquante, le service continue« Pool de connexions à 85 % », « Nouvelle tentative n°2 »
ERRORUne opération a échoué et mérite une investigation« Délai dépassé sur la connexion à la base »
FATALErreur irrécupérable, le processus va s'arrêter« Fichier de configuration illisible au démarrage »

Le niveau est un engagement envers l'équipe d'astreinte : un WARN mal calibré produit du bruit, un ERROR manquant masque un problème réel. Deux questions suffisent à trancher la plupart des cas, à condition de les poser dans l'ordre indiqué.

  1. Le service fonctionne-t-il encore pour cette requête ? Si oui, et que la situation reste anormale (nouvelle tentative réussie, ressource presque pleine), c'est un WARN. Si l'opération a échoué, c'est au moins un ERROR. Un refus métier attendu, comme un mot de passe invalide, n'est pas un échec du service : c'est un INFO. Côté sécurité, des échecs d'authentification répétés signalent peut-être une attaque par force brute : on les compte et on alerte sur le taux, plutôt que de les promouvoir en ERROR un par un.

  2. Le processus peut-il continuer ? Si l'erreur empêche le service de tourner, au démarrage par exemple, c'est un FATAL. Sinon l'ERROR suffit : la requête suivante peut réussir, et c'est le taux d'erreurs, mesuré par une métrique, qui décidera d'une alerte.

En production, la grande majorité des lignes devrait être de niveau INFO, les WARN devraient rester minoritaires, et chaque ERROR devrait mériter qu'on le regarde. Un volume de WARN ou d'ERROR qui ne déclenche jamais aucune action signale un calibrage à revoir, pas un service malade.

Pour les systèmes qui utilisent syslog, le tableau fait correspondre les huit niveaux de la RFC 5424 aux cinq niveaux applicatifs. Retenez surtout le sens des codes : ils croissent vers le moins grave, 0 étant le plus critique et 7 le plus bavard.

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

Ce sens inversé est une source classique d'erreurs de filtrage. Ainsi, journalctl -p 3 ne montre pas seulement les messages de niveau 3 : il affiche tous ceux dont la priorité est inférieure ou égale à 3, donc Error, Critical, Alert et Emergency.

Un log structuré utile contient au minimum six champs, qui répondent aux questions du diagnostic : quand, avec quelle gravité, où, quoi, et comment le relier aux autres signaux. Sans eux, un log est un fragment ; avec eux, il se filtre, se trie et se raccorde à une trace.

Le tableau donne, pour chacun des six champs, la question à laquelle il répond et un exemple de valeur. Lisez-le comme une liste de contrôle : un champ absent laisse une question du diagnostic sans réponse.

ChampPourquoi il est indispensableExemple
timestampQuand : ordonne les événements"2026-02-07T14:32:15.042Z" (ISO 8601, UTC)
levelGravité : filtrage par sévérité"ERROR"
serviceOù : identifie le composant émetteur"payment-api"
messageQuoi : décrit l'événement"Payment failed: bank provider timeout"
trace_idCorrélation entre services : relie le log à une trace distribuée"4bf92f35…"
request_idCorrélation locale : regroupe les logs d'une requête, même sans tracing"req-789ghi"

Le trace_id est propagé entre les services par le système de tracing distribué, comme le décrit la leçon Les traces. Le request_id est généré à l'entrée, par un reverse proxy ou une passerelle d'API, et transmis dans un en-tête comme X-Request-ID : il relie les logs d'une requête même quand le tracing n'est pas encore en place.

Une heure locale change selon le fuseau de la machine et l'heure d'été, ce qui rend l'ordre des événements invalide dès que deux services ne partagent pas le même réglage. L'encadré donne la règle à appliquer.

Le champ message doit rester lisible et stable dans le temps : les détails variables, comme le montant, l'identifiant ou la durée, vont dans des champs dédiés. Un message stable permet de grouper les logs par type d'événement ; si chaque message contient ses valeurs, il devient unique et le regroupement est impossible.

  • Correct : "Payment failed: bank provider timeout" avec les champs amount et duration_ms
  • À éviter : "Payment of 99.99 EUR failed for user 12345 after 3000ms: bank provider timeout"

Aucun de ces champs n'est obligatoire, mais chacun répond à une question qui revient 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 retour arrière ou le remplacement d'un pod. duration_ms rend la latence analysable sans métrique dédiée. user_id est le plus délicat : il rapproche le log d'une donnée personnelle et impose une pseudonymisation.

ChampUsage
span_idSitue le log dans un span précis de la trace
environmentDistingue production, préproduction et développement
versionIdentifie la version de l'application déployée
hostname, pod_nameLocalise l'instance émettrice
user_idIdentifie l'utilisateur concerné, pseudonymisé
duration_msMesure la durée de l'opération
error_typeCatégorise l'erreur (TimeoutError, ValidationError)

Ce log rassemble les champs indispensables et les complémentaires. Il répond d'un coup d'œil à quand (timestamp), quoi (message et type d'erreur), où (service et instance) et pour qui (identifiant pseudonymisé), et il permet d'ouvrir la trace complète grâce à son trace_id.

{
"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: bank provider timeout",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"request_id": "req-a1b2c3",
"user_id": "usr_8f3a",
"amount": 99.99,
"currency": "EUR",
"error_type": "ProviderTimeoutError",
"duration_ms": 3004
}

Les logs traversent des pipelines de collecte, des stockages partagés et des tableaux de bord ouverts à plusieurs équipes : tout ce qui y est écrit devient lisible par beaucoup de monde, et durable.

Une ligne est répliquée, indexée et sauvegardée dans la minute qui suit son émission : la retirer après coup impose de purger l'index, les sauvegardes et les exports vers un SIEM, sans certitude d'avoir tout couvert. Un secret parti dans les logs est un secret à révoquer, pas à effacer.

Le tableau classe les données à bannir par catégorie et indique le risque associé. Il sert de grille en revue de code : une valeur qui entre dans l'une de ces lignes ne doit pas être journalisée en clair.

CatégorieExemplesRisque
SecretsMots de passe, jetons d'API, clés privéesCompromission du système
En-têtes d'authentificationAuthorization, Cookie, Set-CookieSessions et jetons exposés en clair
Données personnellesE-mail, adresse postale, téléphone, adresse IP (arbitrage ci-dessous)Non-conformité au RGPD
Données financièresNuméro de carte bancaire, IBAN completViolation de PCI DSS
Données de santéDiagnostics, prescriptionsViolation réglementaire

Le masquage s'applique à deux endroits complémentaires. À l'émission, dans le code, c'est le plus sûr : la valeur ne quitte jamais le processus, mais chaque service doit être modifié. Dans le collecteur, on rattrape ce que les applications ont laissé passer, en une seule configuration, au prix d'un traitement appliqué à tout le flux. Une équipe qui ne peut pas reprendre trente services d'un coup commence par le collecteur, puis remonte le masquage dans le code service par service.

  • Masquer à l'émission : "card": "****1234" plutôt que le numéro complet
  • Ne pas journaliser les corps de requêtes HTTP en production : ils contiennent souvent des formulaires remplis par les utilisateurs
  • Filtrer dans le collecteur les champs identifiés comme sensibles
  • Dans le doute, ne pas écrire le champ : on peut toujours l'ajouter ensuite, jamais le retirer des sauvegardes

L'adresse IP demande un arbitrage. C'est une donnée personnelle, mais aussi la première pièce d'une réponse à incident : les journaux de sécurité et d'accès (pare-feu, authentification, serveur web frontal) en ont besoin pour retrouver l'origine d'une attaque. On la masque donc dans les logs applicatifs ordinaires, et on la conserve dans ces journaux dédiés, avec un accès restreint et une durée de conservation encadrée et documentée.

Les logs sont le signal le plus volumineux, parce qu'ils sont les seuls à conserver chaque occurrence sans agrégation. Leur coût se décompose en trois postes qui s'additionnent, et c'est sur chacun d'eux qu'on agit pour le maîtriser : l'ingestion, proportionnelle au volume émis, le stockage, proportionnel au volume multiplié par la durée de conservation, et le requêtage, qui se facture ou se dimensionne à part.

Coût ≈ ingestion (volume) + stockage (volume × rétention) + requêtage

FacteurCe qui l'augmenteLevier
VolumeDEBUG en production, corps de requêtes, messages verbeuxINFO au minimum en production, filtrage à la source
RétentionConservation illimitée « au cas où »Durées par niveau, par exemple courtes pour DEBUG, plus longues pour ERROR
RequêtageIndex plein texte sur tous les champsIndexer seulement les champs utiles au filtrage

Le facteur le plus simple à régler est la rétention : ramener les logs de 90 à 30 jours divise le stockage par trois sans toucher une ligne de code. Réduire le volume émis demande au contraire de reprendre l'instrumentation service par service, mais c'est le seul levier qui réduit aussi le coût de collecte et d'indexation.

Comment un log voyage-t-il de l'application au requêtage ?

Section intitulée « Comment un log voyage-t-il de l'application au requêtage ? »

Un log parcourt quatre étapes entre son émission et le moment où quelqu'un le lit. Savoir où se fait chaque traitement indique où agir : le format et le niveau se décident à l'émission, le filtrage et l'enrichissement à la collecte, le coût au stockage.

  1. Émission. L'application produit le log par une bibliothèque de journalisation, comme structlog en Python, slog en Go ou pino en Node.js. C'est là que se décident le format et le niveau. En conteneur, les logs sont écrits sur la sortie standard, et le moteur d'exécution les capture.

  2. Collecte. Un agent récupère les logs depuis un fichier, la sortie standard ou une socket syslog, puis les transmet. C'est l'étape où l'on filtre, où l'on ajoute des métadonnées comme le nom du pod, et où l'on limite le débit des messages trop fréquents.

  3. Stockage et indexation. Deux familles de backends coexistent. Les uns indexent le contenu des logs, avec une recherche plein texte puissante mais coûteuse ; les autres n'indexent que quelques labels et parcourent le contenu au moment de la requête, plus économe mais plus lent sur de gros volumes.

  4. Requêtage. L'opérateur interroge les logs par un langage de requête, depuis une interface d'exploration. C'est là que la structure paie : un log JSON se filtre par champ en quelques secondes, un texte libre impose des expressions régulières lentes et fragiles.

Trois chaînes de collecte de logs, de l'application à l'interface : stdout vers Promtail ou Fluent Bit, Loki puis Grafana ; fichier .log vers Fluentd ou l'OTel Collector, Elastic puis Kibana ; syslog UDP vers rsyslog ou syslog-ng, un fichier puis grep ou jq.Applicationstdout/stderrfichier .logsyslog (UDP)CollecteurPromtailFluent BitFluentdOTel Collectorrsyslogsyslog-ngBackendLokiElasticfichierInterfaceGrafanaKibanagrep / jq
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Le chemin varie selon l'environnement, et ce qui distingue vraiment les variantes, c'est l'endroit où les lignes attendent, donc où elles se perdent quand tout sature. En conteneur, le moteur d'exécution retient la sortie standard et peut perdre des lignes à la rotation de fichier. Sur une machine virtuelle, le fichier absorbe les à-coups mais expose au disque plein si la rotation n'est pas configurée. En syslog sur UDP, il n'y a aucun accusé de réception : un collecteur saturé perd les messages en silence, d'où le choix de TCP dès que les logs servent de piste d'audit, c'est-à-dire de trace exploitable d'une action sensible : qui l'a faite (identité), quoi (action et ressource), quand (horodatage fiable) et avec quel résultat, dans un journal protégé contre la modification.

Dans la formation, le lab Loki fait parcourir ces quatre étapes sur un cluster Kubernetes, et la leçon Loki hors Kubernetes, avec Alloy les reprend sur une machine seule, avec l'agent Alloy qui remplace l'ancien collecteur.

Cette liste se relit en revue de code, pas après la mise en production : chaque point manquant coûte plus cher à rattraper une fois les logs émis en volume. Les deux premiers bloquent l'ingestion elle-même, puisqu'un log sur plusieurs lignes ou un horodatage sans fuseau produit des entrées mal découpées ou mal ordonnées dans le backend.

  • Un événement, un log JSON : pas de log sur plusieurs lignes, la pile d'appels va dans un champ dédié
  • Horodatage en UTC et en ISO 8601, jamais en heure locale
  • Niveau calibré : pas de WARN partout, pas d'ERROR pour un cas normal
  • service, version et environment renseignés
  • trace_id ou request_id présent pour la corrélation
  • Aucun secret ni donnée personnelle en clair, ni dans le message, ni dans les champs
  • Pas de charge utile volumineuse : ni corps HTTP complet, ni objet sérialisé en entier
  • Message court et stable, les valeurs 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 coûte le plus cher quand on la corrige tard : compter des occurrences par recherche textuelle revient à stocker des millions de lignes pour obtenir un seul nombre.

PiègePourquoi c'est un problèmeQue faire
Logger en texte libreFiltrage par expressions régulières fragilesAdopter le JSON avec des champs nommés
Oublier le trace_idLes logs restent isolés des tracesInjecter le trace_id depuis le contexte de tracing
Logger les corps de requêtesVolume énorme et données personnellesNe garder que méthode, chemin, statut et durée
Pas de convention de nommageuserId, user_id et UserID dans trois servicesUn schéma d'équipe, en snake_case
Ignorer la rétentionLes logs s'accumulent et la facture suitUne durée de conservation par niveau
Compter avec les logsLent et imprécisExposer un compteur en métrique

Les références ci-dessous sont les documents d'origine des faits avancés dans cette page : à consulter pour vérifier un chiffre, une définition ou une syntaxe auprès de ses auteurs.

  • Un log est un événement discret et horodaté : il garde le détail individuel, là où la métrique agrège
  • Un log structuré en JSON se filtre par champ ; un texte libre ne se fouille qu'avec des expressions régulières
  • Les six champs indispensables : timestamp (UTC, ISO 8601), level, service, message, trace_id, request_id
  • Le niveau est un engagement envers l'astreinte : WARN si le service continue, ERROR si l'opération échoue, FATAL si le processus s'arrête
  • On ne journalise jamais de secret, d'en-tête d'authentification ni de donnée personnelle en clair
  • Le coût additionne ingestion, stockage (volume × rétention) et requêtage ; le DEBUG permanent en production fait grimper les deux premiers

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn