Une requête utilisateur traverse souvent 5, 10, voire 20 services avant de produire une réponse. Quand la latence explose ou qu’une erreur survient, les logs de chaque service montrent leur fragment de l’histoire — mais personne ne voit le trajet complet. Le tracing distribué résout ce problème : il reconstitue le parcours intégral d’une requête à travers tous les services, avec les durées de chaque étape. Vous identifiez en quelques secondes où le temps est perdu et pourquoi.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Le problème : pourquoi les logs seuls ne suffisent pas pour diagnostiquer la latence dans un système distribué
- Le span : l’unité de travail — nom, durée, parent, attributs, status
- La trace : l’arbre de spans formant le parcours complet d’une requête
- La propagation de contexte : comment le
trace_idtraverse les frontières de services (W3C Trace Context, headertraceparent) - Le sampling : head-based vs tail-based — compromis volume/visibilité
- Exemple visuel : anatomie d’une trace HTTP → API → Base de données
Le problème : reconstituer le parcours d’une requête
Section intitulée « Le problème : reconstituer le parcours d’une requête »Imaginez un site e-commerce. Un client clique sur « Payer ». Cette action déclenche une chaîne :
- Le frontend envoie une requête HTTP
- L’API Gateway authentifie et route
- Le service commande crée la commande
- Le service paiement contacte le prestataire bancaire
- Le service stock décrémente l’inventaire
La réponse met 4,2 secondes. Le client abandonne. Où est le goulot d’étranglement ?
Chaque service a ses propres logs. Vous pouvez chercher dans chacun, mais :
- Les horloges ne sont pas parfaitement synchronisées entre serveurs
- Vous n’avez aucun identifiant commun pour relier les logs entre eux
- Vous voyez des fragments, jamais le trajet complet
C’est exactement le problème que le tracing distribué résout. L’idée vient de Dapper, le système de tracing interne de Google décrit dans un article de recherche en 2010. Le concept a ensuite été standardisé par le projet OpenTelemetry et le W3C.
Le span : l’unité de travail
Section intitulée « Le span : l’unité de travail »Un span (littéralement « empan », une étendue) représente une opération unique dans le traitement d’une requête. Pensez-y comme un chronomètre que vous démarrez au début d’une opération et arrêtez à la fin.
Anatomie d’un span
Section intitulée « Anatomie d’un span »Chaque span contient au minimum :
| Champ | Description | Exemple |
|---|---|---|
trace_id | Identifiant commun à tous les spans d’une même requête (128 bits) | 4bf92f3577b34da6a3ce929d0e0e4736 |
span_id | Identifiant unique de ce span (64 bits, 16 hex) | 00f067aa0ba902b7 |
parent_span_id | ID du span parent (vide pour le root span, 64 bits, 16 hex) | b9c7c989f97918e1 |
name | Nom de l’opération | POST /api/orders |
start_time | Horodatage de début (haute précision, nanoseconde en OTel) | 2026-02-07T10:15:32.123456Z |
duration | Durée de l’opération | 245ms |
status | Résultat : OK, ERROR, UNSET | ERROR |
kind | Type de span (voir ci-dessous) | SERVER |
attributes | Paires clé-valeur de métadonnées | http.method=POST, http.status_code=500 |
Les cinq types de spans (SpanKind)
Section intitulée « Les cinq types de spans (SpanKind) »OpenTelemetry définit cinq types qui décrivent le rôle du span dans la communication :
| SpanKind | Rôle | Exemple |
|---|---|---|
CLIENT | Appel sortant vers un autre service | Requête HTTP vers le service paiement |
SERVER | Traitement d’un appel entrant | Réception de la requête par le service paiement |
INTERNAL | Opération locale, sans communication réseau | Calcul du montant TTC |
PRODUCER | Envoi d’un message asynchrone (file d’attente) | Publication d’un événement dans Kafka |
CONSUMER | Réception d’un message asynchrone | Lecture d’un message depuis Kafka |
La paire CLIENT / SERVER est la plus courante : un service crée un span
CLIENT quand il appelle, le service appelé crée un span SERVER quand il
reçoit. Les deux spans partagent le même trace_id.
Attributs : le contexte métier
Section intitulée « Attributs : le contexte métier »Les attributs sont des paires clé-valeur libres qui enrichissent le span. OpenTelemetry définit des conventions sémantiques pour harmoniser les noms :
# Attributs HTTP standardiséshttp.request.method = "POST"http.response.status_code = 201url.full = "https://api.example.com/orders"
# Attributs base de donnéesdb.system = "postgresql"db.statement = "SELECT * FROM orders WHERE id = $1"db.operation.name = "SELECT"
# Attributs métier (libres)order.id = "ORD-2026-4521"order.total_amount = 149.90Bonnes pratiques pour les attributs :
- Utiliser les conventions sémantiques OpenTelemetry quand elles existent
- Ajouter des attributs métier pertinents pour le diagnostic (ID commande, ID utilisateur anonymisé, montant)
- Ne jamais inclure de données sensibles (mots de passe, tokens, PII non anonymisées)
- Limiter le nombre d’attributs par span (au-delà de 128, la plupart des backends tronquent)
La trace : l’arbre complet
Section intitulée « La trace : l’arbre complet »Une trace est un ensemble de spans reliés par leurs parent_span_id,
formant un arbre. Le span racine (root span) n’a pas de parent — il
représente le point d’entrée de la requête dans le système.
Exemple visuel : commande e-commerce
Section intitulée « Exemple visuel : commande e-commerce »Reprenons le scénario du paiement. Voici la trace complète, visualisée comme un diagramme de Gantt (ce que vous verrez dans Jaeger, Tempo ou Zipkin) :
trace_id: 4bf92f3577b34da6a3ce929d0e0e4736
Service Span Durée Statut─────────────────────────────────────────────────────────API Gateway POST /checkout 4200ms ERROR├─ Svc Commande createOrder 320ms OK├─ Svc Paiement processPayment 3600ms ERROR│ ├─ Paiement validateCard 45ms OK│ └─ Paiement callBankAPI 3500ms ERROR ← goulot└─ Svc Stock decrementStock 80ms OKLecture de la trace :
- Le root span est
POST /checkout(4200 ms au total) - Le service commande (
createOrder) prend 320 ms — normal - Le service paiement prend 3600 ms, dont 3500 ms dans l’appel à l’API bancaire — c’est le goulot
- Le service stock est rapide (80 ms)
- Le statut
ERRORse propage :callBankAPI→processPayment→POST /checkout
Sans trace, vous auriez vu une erreur 500 dans les logs du Gateway, et vous
auriez cherché dans 5 services différents. Avec la trace, vous identifiez
callBankAPI en quelques secondes.
Propagation de contexte : le fil d’Ariane
Section intitulée « Propagation de contexte : le fil d’Ariane »Le tracing distribué ne fonctionne que si chaque service transmet le
trace_id au suivant. C’est la propagation de contexte (context
propagation).
Le standard W3C Trace Context
Section intitulée « Le standard W3C Trace Context »Avant 2020, chaque outil de tracing utilisait ses propres headers :
X-B3-TraceId (Zipkin), uber-trace-id (Jaeger), X-Cloud-Trace-Context
(Google). Résultat : si deux services utilisaient des outils différents, la
trace était coupée.
Le W3C a standardisé deux headers HTTP avec la spécification Trace Context 1.0 (Recommendation du 23 novembre 2021) :
traceparent : le header principal. Format :
traceparent: {version}-{trace-id}-{parent-id}-{trace-flags}
Exemple :traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 │ │ │ │ │ │ │ └─ flags (2 hex, 01 = sampled) │ │ └─ parent-id (16 hex = SpanId) │ └─ trace-id (32 hex = TraceId) └─ version (2 hex, toujours 00 actuellement)Attention : trace-id fait 32 hex (128 bits) et parent-id fait 16 hex
(64 bits) — ce n’est pas la même taille.
tracestate : header compagnon pour les informations spécifiques à un
vendor (Datadog, New Relic, etc.). Il transporte des paires clé-valeur propres à
chaque outil, sans casser l’interopérabilité.
À côté de traceparent, on rencontre aussi baggage (W3C Baggage) pour
transporter du contexte applicatif entre services — identifiant de tenant,
segment utilisateur, feature flag. À limiter strictement : attention aux PII et
au volume, chaque paire clé-valeur est propagée à tous les services en aval.
Comment ça fonctionne en pratique
Section intitulée « Comment ça fonctionne en pratique »-
Le premier service crée le contexte
Le service qui reçoit la requête initiale (sans
traceparententrant) génère untrace_idet unspan_id, puis les attache à ses appels sortants via le headertraceparent. -
Chaque service propage le contexte
Quand un service reçoit un
traceparent, il extrait letrace_idet leparent-id. Il crée un nouveau span avec le mêmetrace_id, un nouveauspan_id, et leparent-idreçu commeparent_span_id. Il propage letraceparentmis à jour à ses propres appels sortants. -
Le backend recollecte et reconstitue
Chaque service envoie ses spans au collecteur (OTLP, Jaeger, Zipkin). Le backend de tracing rassemble tous les spans d’un même
trace_idet reconstitue l’arbre.
Pourquoi la standardisation compte
Section intitulée « Pourquoi la standardisation compte »| Avant W3C Trace Context | Avec W3C Trace Context |
|---|---|
| Chaque outil a son header propriétaire | Un seul header traceparent universel |
| Trace coupée si deux services utilisent des outils différents | Interopérabilité garantie |
| Migration d’outil = reconfiguration de tous les services | Migration transparente |
| Corrélation manuelle par timestamps | Corrélation automatique par trace_id |
Sampling : contrôler le volume
Section intitulée « Sampling : contrôler le volume »En production, un système peut traiter des millions de requêtes par heure. Traquer chaque requête est coûteux en CPU, en bande passante et en stockage. Le sampling (échantillonnage) sélectionne un sous-ensemble de traces à conserver.
Head-based sampling (décision à l’entrée)
Section intitulée « Head-based sampling (décision à l’entrée) »Le premier service décide au moment où la requête arrive si elle sera tracée
ou non. La décision est encodée dans le flag trace-flags du traceparent
(01 = sampled, 00 = not sampled). Tous les services en aval respectent cette
décision.
Avantages :
- Simple : un seul point de décision
- Coût prévisible : ratio fixe (par exemple 10 % des requêtes)
- Pas de buffering : chaque service sait immédiatement s’il doit exporter ou non
Inconvénient majeur : la décision est prise avant de savoir si la requête sera intéressante. Une erreur rare sur 0,1 % des requêtes a 90 % de chances de ne pas être échantillonnée avec un ratio de 10 %.
Tail-based sampling (décision après collecte)
Section intitulée « Tail-based sampling (décision après collecte) »Un collecteur central (comme l’OpenTelemetry Collector avec le processeur
tail_sampling) reçoit tous les spans, reconstitue les traces complètes, et
décide ensuite lesquelles conserver selon des règles :
- Garder toutes les traces en erreur
- Garder les traces dont la latence dépasse un seuil
- Garder un échantillon aléatoire des traces normales
- Garder les traces contenant certains attributs (utilisateur VIP, endpoint critique)
Avantages :
- Ne rate jamais les erreurs — toute trace anormale est capturée
- Règles intelligentes — décision basée sur le contenu réel de la trace
Inconvénients :
- Coût réseau : tous les spans doivent être envoyés au collecteur (même ceux qui seront rejetés)
- Mémoire : le collecteur doit bufferiser les spans le temps de reconstituer la trace complète (typiquement 30 secondes à quelques minutes)
- CPU côté services : le tail-based réduit le stockage final et améliore la couverture des erreurs, mais les services instrumentent et exportent quand même tous les spans vers le collecteur — le coût d’instrumentation côté application reste non nul
- Complexité : nécessite un collecteur central dimensionné pour le volume total
Comparaison et stratégie mixte
Section intitulée « Comparaison et stratégie mixte »| Critère | Head-based | Tail-based |
|---|---|---|
| Décision | Au premier service | Au collecteur central |
| Coût réseau | Faible (seuls les spans échantillonnés transitent) | Élevé (tous les spans transitent) |
| Couverture des erreurs | Incomplète (échantillonnage aléatoire) | Complète (règles sur le résultat) |
| Complexité opérationnelle | Faible | Élevée (collecteur à dimensionner) |
| Mémoire | Négligeable | Significative (bufferisation) |
En pratique, les deux approches se combinent : head-based sampling pour éliminer un pourcentage de traces triviales au plus tôt (économie de bande passante), puis tail-based sampling au collecteur pour appliquer des règles intelligentes sur les traces restantes.
Coût et volumétrie
Section intitulée « Coût et volumétrie »Un span pèse typiquement entre 200 et 500 octets selon le nombre d’attributs. Quelques ordres de grandeur :
| Volume de requêtes | Spans/min (3 spans/requête) | Stockage brut/jour |
|---|---|---|
| 100 rps | 18 000 | ~5 Go |
| 1 000 rps | 180 000 | ~50 Go |
| 10 000 rps | 1 800 000 | ~500 Go |
Ces chiffres représentent le volume brut des spans, hors compression, index et métadonnées du backend. Le coût réel de stockage dépend de votre solution (Tempo, Jaeger, Elasticsearch…), mais l’ordre de grandeur reste valable pour dimensionner le sampling. Sans échantillonnage, un service à 10 000 rps génère plusieurs centaines de Go/jour de données de tracing — souvent plus que les logs et métriques combinés.
Pièges courants
Section intitulée « Pièges courants »| Piège | Pourquoi c’est un problème | Solution |
|---|---|---|
| Trace coupée à un service | Un service ne propage pas le traceparent (proxy, load balancer, middleware) | Vérifier que chaque composant du chemin propage les headers W3C |
| Confondre trace applicative et trace distribuée | Les « traces » de debug (niveau TRACE dans les logs) n’ont rien à voir avec le tracing distribué | Utiliser le terme span ou trace distribuée pour éviter l’ambiguïté |
| Trop d’attributs par span | Au-delà de ~128 attributs, les backends tronquent et le coût explose | Se limiter aux attributs nécessaires au diagnostic + conventions sémantiques |
| Pas de sampling | 100 % de tracing à fort volume = coût réseau et stockage prohibitif | Mettre en place du head-based puis du tail-based progressivement |
| Instrumenter uniquement les appels HTTP | Les appels à la base de données, au cache, aux files d’attente restent invisibles | Instrumenter aussi les clients DB, Redis, Kafka avec les SDK OTel |
| Ignorer les appels asynchrones | La trace s’arrête au PRODUCER — le CONSUMER crée une trace séparée | Propager le contexte dans les headers de message, ou utiliser des Span Links quand la relation n’est pas parent/enfant (batching, fan-out, async) mais qu’on veut relier les contextes |
À retenir
Section intitulée « À retenir »-
Une trace est un arbre de spans reliés par un
trace_idcommun — elle reconstitue le parcours complet d’une requête -
Un span est l’unité de travail : nom de l’opération, durée,
parent_span_id, attributs et status -
La propagation de contexte repose sur le header HTTP
traceparent(W3C Trace Context) au format{version}-{trace-id}-{parent-id}-{trace-flags} -
Le sampling contrôle le volume : head-based (décision à l’entrée, simple, ratio fixe) ou tail-based (décision au collecteur, intelligent mais coûteux en ressources)
-
Les deux types de sampling se combinent : head-based pour réduire la bande passante, tail-based pour capturer toutes les anomalies
-
Le
trace_idest le pont entre les trois signaux : il relie les spans aux logs (champtrace_iddans le log structuré) et aux métriques (exemplars)
- Google Dapper : Dapper, a Large-Scale Distributed Systems Tracing Infrastructure — article fondateur (2010)
- W3C Trace Context : w3.org/TR/trace-context — spécification du header
traceparent - OpenTelemetry Traces : opentelemetry.io/docs/concepts/signals/traces — documentation officielle des spans et traces
- OpenTelemetry Semantic Conventions : opentelemetry.io/docs/specs/semconv — conventions de nommage des attributs