Aller au contenu principal
GiwiSoft
08

Logs vs metrics vs traces : enfin comprendre la différence

Giwi 11 min de lecture DevOps, Monitoring

Je vois encore beaucoup de confusion autour des trois piliers de l’observabilité : les logs, les métriques et les traces. On utilise souvent le mot “monitoring” pour tout, et on finit par instrumenter n’importe comment. Dans cet article, je mets enfin les choses au clair : ce que sont ces trois types de données, ce qu’ils mesurent chacun, quand les utiliser, et comment ils se complètent.

La clé tient en une phrase : ce sont trois angles de vue différents sur le même système. Le log dit ce qui s’est passé, la métrique dit combien de fois et en combien de temps, la trace dit par où est passé le temps. Ce ne sont pas des concurrents, ce sont des vues complémentaires qu’on corrèle.

Les logs : des événements discrets

Un log est un événement discret daté. Il répond à la question “que s’est-il passé à cet instant précis ?”. C’est le type de donnée le plus ancien et le plus simple à comprendre : une ligne, ou un objet JSON, qui décrit un fait.

2026-12-12T09:01:22.184Z INFO  user=42 action=login ip=203.0.113.5
2026-12-12T09:01:22.410Z ERROR user=42 action=checkout msg="payment gateway timeout"
2026-12-12T09:01:23.050Z INFO user=42 action=checkout status=retry attempt=2

Les caractéristiques fondamentales des logs :

  • Riches et descriptifs : ils portent du contexte, des valeurs, des messages. On peut y retrouver un user_id, une erreur, un détail métier.
  • Non structurés ou semi-structurés : dans le meilleur des cas JSON (recommandé), mais parfois juste du texte libre.
  • Volumineux et peu compactés : un service actif produit des millions de logs par jour, il faut de gros volumes de stockage et du filtrage efficace.
  • Pas natifs à l’agrégation : pour connaître un compte (combien d’erreurs ?), il faut compter des lignes, ce qui est coûteux.

Le log est la source de détail : quand quelque chose a mal tourné, c’est lui qui contient le pourquoi granulaire. Mais il est mauvais pour la vue d’ensemble.

Quand utiliser les logs : le diagnostic fin, le débogage, la conformité (audit), la recherche d’une occurrence précise avec un grep sémantique. Si vous devez répondre à “montre-moi tout ce qui s’est passé pour cet utilisateur”, vous voulez des logs.

Les métriques : des agrégations numériques

Une métrique est une mesure numérique agrégée sur le temps. Elle répond à la question “combien ?” ou “en combien de temps ?”. C’est une série de points (valeur, horodatage), souvent taguée par des labels.

Une métrique est native à la série temporelle : elle est conçue pour être agrégée, moyennée, passée en percentile, affichée en graphe. C’est le contraire du log.

Il existe trois grands types de métriques :

  • Compteur (counter) : une valeur qui ne fait que croître (nombre total de requêtes, de tokens, d’erreurs). Pour obtenir un débit, on calcule sa dérivée (rate).
  • Jauge (gauge) : une valeur qui monte et descend (température, profondeur de file, nombre de connexions actives). Couramment mesurée à l’instant T.
  • Histogramme (histogram) : une distribution de valeurs (temps de réponse) dont on dérive les percentiles p50, p95, p99.

Exemple de séries de métriques :

http_requests_total{method="GET",status="200"}  1024   # compteur
http_requests_total{method="POST",status="500"} 7 # compteur
queue_depth{} 12 # jauge
http_request_duration_seconds_bucket{le="0.1"} 980 # histogramme

Les caractéristiques clés :

  • Compactes et bon marché : une métrique occupe quelques octets, contrairement à un log. On peut les conserver très longtemps.
  • Optimisées pour l’agrégation : le graphe, l’alerte, le tableau de bord sont leur terrain de jeu naturel.
  • Pauvres en contexte : une métrique vous dit “500 erreurs” mais pas “pour tel utilisateur, avec telle erreur”. Le label donne un contexte limité, rien de plus.

La métrique est la source de tendance et d’alerte : elle répond à “le système va-t-il bien en ce moment ?” et constitue la base de tous les dashboards et alertes.

Quand utiliser les métriques : la surveillance continue, les alertes, la capacité, l’analyse de tendance, le coût. Si vous devez répondre à “quelle est la latence moyenne et le taux d’erreur de ce service cette semaine”, vous voulez des métriques.

Les traces : le cycle de vie d’une requête

Une trace décrit le cycle de vie d’une requête à travers les services. Elle répond à la question “où est passé le temps ?”. C’est l’outil des architectures distribuées.

Une trace est composée de spans : chaque span représente une unité de travail (un appel HTTP, une requête de base de données, un appel de service externe) avec son nom, sa durée, son début et sa fin. Les spans sont hiérarchiques : un span parent (la requête initiale) contient des spans enfants (chaque appel effectué pour la servir).

Trace: GET /orders/42
├─ span: api-gateway duration: 120ms
│ ├─ span: auth-service duration: 15ms
│ ├─ span: orders-service duration: 85ms
│ │ ├─ span: db-query duration: 40ms
│ │ └─ span: cache-get duration: 8ms
│ └─ span: invoice-service duration: 20ms

Les caractéristiques clés :

  • Hiérarchique : montre la relation parent-enfant entre les étapes.
  • Corrélée par un identifiant : chaque span porte un trace_id partagé, qui relie tous les spans d’une même requête, même à travers les services.
  • Conçue pour mesurer le temps : d’où le temps de chaque étape, indispensable pour le profilage.

La trace est la source de parcours et de diagnostic de latence : elle répond à “pourquoi cette requête précise était lente, et où est passé le temps ?”. Une trace révèle qu’un service que vous ne soupçonniez pas (et non le plus chargé) est le vrai goulot d’étranglement.

Quand utiliser les traces : le diagnostic de latence end-to-end, le profilage distribué, l’analyse d’une requête spécifique lente. Si vous devez répondre à “où a passé le temps cette requête qui a mis 5 secondes ?”, vous voulez des traces.

Le problème de cardinalité

Un concept central pour choisir où mettre une donnée est la cardinalité, c’est-à-dire le nombre de valeurs distinctes que peut prendre un identifiant.

Prenons l’exemple d’un user_id. Il y a des millions de valeurs possibles distinctes. Si vous en faites un label de métrique, chaque valeur distincte crée une nouvelle série temporelle, et le nombre de séries explose à l’échelle. C’est le célèbre problème de cardinalité de Prometheus : il faut limiter les labels à basse cardinalité (méthode HTTP, statut, nom de service) et ne jamais y mettre d’identifiants à haute cardinalité (user_id, request_id, IP).

La règle que je suis :

  • Haute cardinalité (user_id, request_id, transaction id) → logs : le log est fait pour ça, il n’a aucun problème à contenir un identifiant distinct par ligne.
  • Basse cardinalité (status, method, model, tool, region) → métriques : parfait comme label.
  • Traces : un compromis, le trace_id est de haute cardinalité mais vit dans les spans, pas dans des labels de métrique.

Si vous connaissez le concept de cardinalité, vous comprenez pourquoi on ne peut pas tout mettre dans les métriques : ça exploserait le stockage. D’où la complémentarité logique entre logs et métriques.

Comment ils se complètent : la corrélation

Les trois piliers ne prennent tout leur sens que corrélés. L’idée maîtresse est de pouvoir passer d’une vue à l’autre sans perdre le fil d’une même requête.

La façon la plus simple de corréler est d’ajouter le même identifiant partout. Une requête reçoit un trace_id (et un request_id). Ce même identifiant est :

  • ajouté à chaque log généré pendant la requête (en champ JSON),
  • visible dans les spans de la trace,
  • et, si besoin, référencé dans une métrique pour y accéder.

Ainsi, on peut partir d’un graphe de métriques (le p99 de latence grimpe), cliquer pour voir les traces lentes de la période, prendre une trace, et de là ouvrir les logs correspondants. C’est exactement le workflow que je décris dans mon article sur OpenTelemetry. La corrélation est le ciment des trois piliers.

Un exemple complet : une seule requête

Prenons un exemple concret pour montrer comment une seule requête utilisateur génère des logs, met à jour des métriques, et construit une trace. L’utilisateur clique “panier” sur une boutique en ligne, ce qui appelle le service de commande, qui lui-même appelle la base de données et un service de paiement externe.

La trace

Une trace unique est créée, avec un trace_id (par exemple abc123). Elle contient des spans hiérarchiques :

Trace: abc123 — POST /orders
├─ span: gateway ctx=trace=abc123 duration 15ms
│ ├─ span: orders-service ctx=trace=abc123 duration 110ms
│ │ ├─ span: db transaction duration 35ms
│ │ └─ span: payment-service duration 60ms
│ │ └─ span: http call external duration 55ms

Chaque span porte un identifiant de parent, ce qui reconstitue l’arbre. C’est la trace qui montre que les 60 ms de paiement sont le plus gros poste, et qu’en les regardant de plus près, 55 ms sont passées dans l’appel HTTP externe, pas dans votre code.

Les logs générés

Le long de cette requête, plusieurs logs sont émis, chacun tagué avec le trace_id :

2026-12-12T09:02:01.001Z  INFO  trace=abc123 svc=orders   event="order_received" order_id=7741
2026-12-12T09:02:01.036Z INFO trace=abc123 svc=orders event="db_commit" rows=1
2026-12-12T09:02:01.061Z INFO trace=abc123 svc=payment event="payment_started" gateway=stripe
2026-12-12T09:02:01.116Z INFO trace=abc123 svc=payment event="payment_ok" id=pi_9xy amount=49.90
2026-12-12T09:02:01.118Z INFO trace=abc123 svc=orders event="order_confirmed" order_id=7741

Ces logs contiennent des détails que ni la trace ni les métriques ne portent : l’order_id, le gateway, le montant. Si le paiement échoue, c’est dans un log qu’on trouvera le code d’erreur exact du prestataire, corrélé à la trace par le trace_id.

Les métriques mises à jour

Pendant ce temps, les mêmes événements incrémentent des métriques agrégées. Plusieurs compteurs et jauges changent :

orders_total{status="success"}            +1          # compteur
orders_total{status="success",zone="eu"} +1 # compteur
payment_total{gateway="stripe",ok="true"} +1 # compteur
http_request_duration_seconds_bucket{le="0.2"} +1 # histogramme
queue_depth{} inchangé # jauge

Ces métriques ne contiennent ni trace_id, ni order_id : elles ont une cardinalité basse (statut, gateway, service). Elles ne savent pas quelle commande précise, mais elles permettent de répondre “combien de commandes réussies cette heure ? à quelle latence ?” de façon extrêmement bon marché.

Le tableau de synthèse

Type Question répondu Cardinalité Coût de stockage Usage principal
Logs Que s’est-il passé ? Haute (OK) Élevé Diagnostic fin, débogage, audit
Métriques Combien / en combien de temps ? Basse (label) Faible Dashboards, alertes, tendances
Traces Où est passé le temps ? Haute (trace_id) Moyen Profilage distribué, latence end-to-end

En résumé, pour cette requête unique : la trace la reconstitue de bout en bout, les logs enrichissent chaque étape de détails métier, et les métriques glissent une goutte dans le grand réservoir numérique qui alimente les graphes. Ce ne sont pas trois systèmes concurrents, c’est trois regards sur le même événement.

Les pièges à éviter

Voici les erreurs que je répète avoir vues, et comment les éviter.

Mettre des métriques partout

Le piège inverse : vouloir tout mesurer en métrique, y compris les détails riches. On finit avec des labels à haute cardinalité (user_id dans un label) et un stockage qui explose, ou des métriques illisibles qui ne disent rien de précis.

Remède : métriques pour les tendances à faible cardinalité, logs pour le détail. Si vous avez besoin du détail d’une occurrence, c’est un log, pas une métrique.

Faire de la traçage excessif sur un monolithe

Pour une application monolithique sur un seul processus, les traces apportent peu : il n’y a pas de frontière de service à traverser, et la latence se diagnostique très bien avec des logs horodatés et des métriques. Ajouter toute la mécanique de traçage distribué (span contextes, traçage propagé) est un surcoût sans bénéfice.

Remède : sur un monolithe, loguez avec soin, mesurez les métriques de latence, et oubliez les traces tant qu’il n’y a pas de vrai service séparé. La trace devient indispensable quand vous avez plusieurs services qui s’appellent entre eux.

Des logs sans corrélation

Des logs volumineux mais sans trace_id ni request_id commun sont quasi inexploitables pour une requête qui traverse plusieurs services : impossible de reconstituer le fil.

Remède : propagez systématiquement un identifiant commun (request_id / trace_id) dans tous les logs et tous les spans d’une même requête. C’est la condition pour que la corrélation fonctionne.

Des alertes basées uniquement sur les logs

Compter des lignes de log pour alerter est lent et peu fiable (le volume, le format, la perte de ligne affectent le résultat). Les alertes doivent reposer sur des métriques, qui sont conçues pour ça.

Remède : les logs pour le diagnostic après coup, les métriques pour l’alerte temps réel.

Réflexion finale

La compréhension de la différence entre les trois piliers m’a permis de construire des systèmes d’observabilité cohérents au lieu d’un tas d’outils empilés. La règle simple que je retiens : métriques pour voir, logs pour comprendre, traces pour retrouver. Chaque outil à sa place, et la corrélation relie le tout.

Si je ne devais en garder qu’une idée, ce serait celle-ci : ne choisissez pas logs ou métriques comme si c’étaient des concurrents. Posez-vous la question : quelle question est-ce que je veux pouvoir répondre ? Choisissez l’outil adapté, sachant que la plupart des bonnes réponses exigeront de mélanger deux, voire trois piliers, reliés par un identifiant commun.

Pour aller plus loin

  • OpenTelemetry unifie logs, métriques et traces dans un même kit d’instrumentation (article dédié).
  • Les metrics d’un LLM : appliquer ces principes aux applications IA (article dédié).
  • Visualisation : une fois les métriques en place, construire un dashboard Grafana.
  • Grafana Tempo, Loki et Prometheus : respectivement traces, logs et métriques, souvent déployés ensemble dans la pile Grafana.