Correlation ID, Trace ID, Span ID : comprendre le squelette de l'observabilité distribuée
"Le bug est en prod, l'utilisateur a renvoyé un identifiant de requête dans un ticket support, et tu as 8 microservices, un broker Kafka et un webhook PSP à inspecter. Par où tu commences ?"
Cette question, n'importe quel développeur backend travaillant sur des systèmes distribués l'a déjà rencontrée.
Et c'est précisément pour répondre à ce problème qu'existent les notions de correlation ID, trace ID, span ID et, plus largement, les mécanismes d'observabilité distribuée.
Pendant longtemps, j'ai moi-même considéré le correlationId et le traceId comme deux variantes du même concept. Mais en construisant une petite librairie Spring Boot d'observabilité — capable de corréler les requêtes HTTP, les appels inter-services et même les requêtes SQL Hibernate — je me suis rendu compte qu'ils ne résolvaient pas exactement le même problème.
Cet article propose donc une exploration pragmatique de ces concepts, en partant d'un besoin très concret : comment garder un fil narratif cohérent dans un système où chaque service ne voit qu'une fraction de l'histoire ?
1. Le problème : dans un système distribué, les logs seuls ne racontent plus l'histoire
Dans un monolithe classique, une requête utilisateur produit une suite d'événements dans :
- un seul processus,
- une seule machine,
- un seul fichier de logs.
Avec un peu de patience, on peut souvent reconstituer l'exécution :
- via le thread name,
- les timestamps,
- ou quelques logs métier.
Mais dans un système distribué, la même action utilisateur peut traverser :
- plusieurs services,
- plusieurs bases de données,
- plusieurs machines,
- parfois des brokers,
- parfois des systèmes tiers.
Exemple :
Sans mécanisme de corrélation explicite :
- impossible de reconstituer le chemin complet d'une requête,
- impossible de relier une requête SQL lente à l'appel HTTP qui l'a déclenchée,
- impossible de savoir quel workflow métier a produit tel événement.
Les timestamps ne suffisent pas : deux utilisateurs peuvent appeler simultanément le même endpoint et produire des logs totalement entrelacés.
Il faut donc introduire des identifiants capables de traverser les frontières techniques.
2. Première confusion classique : correlation ID et trace ID ne sont pas la même chose
Pendant longtemps, beaucoup d'équipes ont utilisé un simple :
X-Correlation-ID: abc123
propagé de service en service.
Puis OpenTelemetry, Zipkin, Jaeger et les standards W3C sont arrivés avec :
traceId,spanId,traceparent.
À première vue, les deux semblent faire la même chose :
identifier une requête à travers plusieurs services.
Mais en réalité : ils opèrent à deux niveaux différents.
3. Le trace ID : la continuité technique
Le traceId appartient au monde du tracing distribué.
Il représente :
une exécution technique continue.
Exemple :
Dans cette chaîne :
- tous les services partagent le même
traceId, - chaque opération locale possède son propre
spanId.
Exemple simplifié :
Le traceId permet donc :
- de reconstruire l'arbre technique d'une exécution,
- de mesurer les durées,
- d'afficher des waterfalls,
- de visualiser les dépendances inter-services.
C'est exactement ce que font :
- Jaeger,
- Tempo,
- Datadog APM,
- Zipkin.
4. Le span ID : l'unité de travail locale
Un span représente une opération locale :
- une requête HTTP,
- un appel SQL,
- un appel sortant,
- un traitement métier.
Chaque span possède :
- un
spanId, - un
parentSpanId, - une durée,
- des métadonnées.
Exemple :
Le traceId relie toute la trace.
Les spanId structurent l'arbre interne.
5. Le vrai rôle du correlation ID : la continuité métier
C'est ici que la subtilité apparaît.
Le traceId suit une exécution technique continue.
Mais un workflow métier réel dépasse souvent une seule exécution technique.
Prenons un paiement avec un PSP externe :
À ce moment :
- le
traceIdOpenTelemetry existe, - tout est cohérent techniquement.
Mais quelques secondes plus tard :
Une nouvelle requête HTTP démarre.
Donc :
- nouveau thread,
- nouvelle exécution,
- nouveau
traceId.
Et c'est parfaitement normal.
Le PSP :
- ne participe pas à ton runtime,
- ne propage pas forcément ton contexte OpenTelemetry,
- possède sa propre infrastructure.
Donc :
et :
sont deux traces techniques distinctes.
Pourtant : métierment, il s'agit toujours du même paiement.
C'est précisément là qu'intervient le correlationId.
6. Le correlation ID : le fil rouge métier
Le correlationId représente :
l'identifiant global du workflow métier.
Contrairement au traceId, il :
- peut survivre à plusieurs requêtes,
- plusieurs traces,
- plusieurs systèmes,
- plusieurs moments temporels.
Exemple :
correlationId = PAY-2026-XYZ
Puis :
Tous ces événements appartiennent au même workflow métier :
correlationId = PAY-2026-XYZ
Le traceId suit le runtime.
Le correlationId suit le métier.
7. Pourquoi les PSP possèdent des champs "reference"
C'est ici qu'on comprend enfin le vrai rôle de champs comme :
merchantReference,externalReference,clientReference,orderReference.
Ils ne servent pas uniquement à "mettre un numéro de commande".
Ils servent surtout à :
corréler un workflow métier entre plusieurs systèmes indépendants.
Exemple :
Appel initial vers le PSP
{
"amount": 100,
"merchantReference": "PAY-2026-XYZ"
}
Puis plus tard :
Webhook retour
{
"merchantReference": "PAY-2026-XYZ",
"status": "PAID"
}
Ton système peut alors :
- retrouver le workflow métier,
- rattacher une nouvelle trace technique,
- reconstruire toute l'histoire du paiement.
C'est exactement ce que permet le correlationId.
8. Notre starter : faire coexister corrélation métier et tracing technique
Notre starter Spring Boot repose sur une idée simple :
Chaque requête transporte donc :
correlationId,traceId,spanId,parentSpanId.
Exemple :
public record TraceContext(
String correlationId,
String traceId,
String spanId,
String parentSpanId,
String serviceName
) {}
9. Le filtre HTTP : point d'entrée du contexte
Un OncePerRequestFilter :
- lit les en-têtes entrants,
- génère les IDs manquants,
- initialise le MDC.
Pseudo-code :
String correlationId = request.getHeader("X-Correlation-ID");
if(correlationId == null) {
correlationId = CorrelationIdGenerator.generate();
}
String traceId =
extractOrGenerateTraceId(request);
MDC.put("correlationId", correlationId);
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
Une fois le MDC initialisé : tous les logs produits pendant la requête héritent automatiquement du contexte.
Sans toucher au métier.
10. Propagation HTTP sortante
Les appels inter-services propagent automatiquement :
X-Correlation-ID,traceparent.
Exemple :
request.getHeaders()
.add("X-Correlation-ID", context.correlationId());
request.getHeaders()
.add("traceparent", TraceParent.format(context));
Le service distant :
- récupère le même
correlationId, - récupère le même
traceId, - génère un nouveau
spanId.
11. Corrélation SQL : le chaînon souvent oublié
La partie la plus intéressante de notre starter est probablement l'instrumentation SQL.
Grâce à StatementInspector Hibernate, nous injectons des commentaires SQL :
/* corr_id=PAY-2026-XYZ trace_id=4bf92f... service=payment-service */
SELECT * FROM payment WHERE order_id = ?
Ces commentaires :
n'affectent pas l'exécution SQL,
mais apparaissent dans :
- les slow query logs,
- PostgreSQL,
- pg_stat_statements,
- les outils DBA.
Résultat : un DBA peut relier une requête lente :
- à un service,
- à un workflow métier,
- à une requête HTTP,
- à un utilisateur.
C'est extrêmement puissant.
12. Pourquoi faire ça alors qu'OpenTelemetry existe déjà ?
Parce qu'OpenTelemetry résout principalement :
- le tracing technique,
- la propagation,
- les spans,
- l'export.
Mais il ne résout pas automatiquement :
- la corrélation métier,
- l'exposition des IDs dans les réponses,
- les commentaires SQL,
- les workflows PSP/webhooks,
- la visibilité simple dans les logs.
Notre starter peut fonctionner :
- seul,
- ou comme complément léger à OpenTelemetry.
Le positionnement n'est donc pas :
remplacer OpenTelemetry
mais plutôt :
rendre le contexte technique et métier immédiatement exploitable dans les logs, SQL et workflows distribués.
13. Résumé
| Concept | Rôle |
|---|---|
traceId |
Identifiant technique distribué d'une exécution continue |
spanId |
Identifiant local d'une unité de travail |
correlationId |
Identifiant métier d'un workflow global |
| MDC | Rend les IDs visibles dans tous les logs |
| SQL comments | Relient les logs DB au workflow métier |
Au fond, toute l'observabilité distribuée repose sur une seule idée :
garder un fil narratif cohérent dans un système fragmenté.
Le traceId raconte l'histoire technique.
Le correlationId raconte l'histoire métier.
Et c'est précisément lorsqu'on combine les deux que les systèmes distribués deviennent réellement observables.