Un service produit des logs dans plusieurs formats, des métriques dans un outil séparé, et rien ne relie une requête lente à l’appel qui l’a provoquée. Pour comprendre un incident, il faut recouper ces sources à la main.

OpenTelemetry répond à ce problème. Il fournit un format standard dans lequel le code décrit ce qu’il fait. Un humain ou un agent exploite ensuite cette télémétrie.

Cet article ouvre une série sur la mise en pratique de l’observabilité, issue d’un retour d’expérience personnel :

  • on installera ensuite une stack auto-hébergée (OpenObserve sur un cluster Scaleway Kapsule),
  • on instrumentera trois services en Go, Rust et Java,
  • puis, on rendra cette télémétrie interrogeable par une CLI
  • enfin, on automatisera l’exploitation de l’observabilité à l’aide d’un agent Claude Code.

Ce premier article pose le vocabulaire commun.

Monitoring et observabilité : questions prévues et questions imprévues

Le monitoring surveille des indicateurs qu’on a choisis à l’avance : CPU, latence p99, taux d’erreur. Il répond bien aux questions qu’on savait poser, les « known unknowns ».

L’observabilité permet de répondre à une question qu’on n’avait pas anticipée, en explorant les données déjà collectées. Ce sont les « unknown unknowns » : pourquoi ce client précis a des timeouts depuis 14h32, alors qu’aucune alerte ne s’est déclenchée.

La page sur les concepts d’OpenTelemetry détaille cette distinction[3].

OpenTelemetry, contrat d’interface entre le code et le backend

OpenTelemetry est le contrat d’interface entre le code et n’importe quel backend. Le code appelle l’API OpenTelemetry et émet sa télémétrie en OTLP. Un Collector OpenTelemetry la reçoit, puis l’exporte vers le backend.

Les logs suivent ce chemin comme les métriques et les traces. Le SDK les envoie au Collector, ou le Collector les lit lui-même (voir la section sur les logs).

Le backend qui reçoit la télémétrie (OpenObserve dans cette série) n’impose pas sa convention de nommage au code. Dans un prochain article, nous développerons trois services dans trois langages qui partageront ainsi un format de log commun.

Logs, métriques, traces et corrélation par trace_id

OpenTelemetry structure la télémétrie en trois signaux :

  • les logs sont des événements horodatés, structurés ou non ;
  • les métriques sont des séries numériques agrégées dans le temps : compteurs, jauges, histogrammes ;
  • les traces forment l’arbre des spans qui représente le parcours d’une requête à travers plusieurs services.

Un quatrième signal, les profiles (profilage continu de CPU et de mémoire), est en statut Development pour l’export OTLP[1].

Les trois signaux sont corrélés par un trace_id et un span_id, qui situent la donnée dans une requête, et par une resource, qui identifie son origine (service.name, service.namespace, service.version, deployment.environment.name).

Ces quatre attributs de resource sont un choix de nommage fait dans un prochain article.

Ils sont posés une fois pour les trois services de la démo, qui héritent partout des mêmes valeurs.

Le trace_id identifie une requête de bout en bout. C’est un identifiant de 16 octets (32 caractères hexadécimaux), généré au premier service qui reçoit la requête et partagé par tous les spans de la trace, quel que soit le service qui les produit[4][11].

Le span_id identifie une opération précise au sein de cette trace : un appel HTTP, une requête SQL, un traitement interne. C’est un identifiant de 8 octets (16 caractères hexadécimaux), propre à chaque span. Chaque span référence le span_id de son parent, ce qui reconstitue l’arbre des appels[15].

Un log émis pendant un span porte le trace_id et le span_id de ce span. Depuis un log, on retrouve ainsi la trace complète et l’opération exacte qui l’a produit[12].

Une trace de trois spans imbriqués partageant le même trace_id, chacun avec son span_id et celui de son parent, et un log rattaché au troisième spantrace_id 4bf92f3577b34da6a3ce929d0e0e4736GET /orders (Go)span_id 00f067aa0ba902b7 · sans parent (span racine)POST /stock (Rust)span_id 53995c3f42cd8ad8 · parent 00f067aa0ba902b7reserve-stock (Java)span_id b7ad6b7169203331 · parent 53995c3f42cd8ad8log « stock insuffisant »trace_id=4bf92f35…4736 span_id=b7ad6b7169203331
Les trois spans partagent le même trace_id. Chacun a son span_id et référence celui de son parent. Le log émis dans reserve-stock porte les deux identifiants.

Deux chemins pour envoyer les logs au Collector

Les logs arrivent au Collector de deux façons :

  • l’application les émet en OTLP : les SDK fournissent un pont (log bridge) vers les bibliothèques de logging existantes (slog en Go, tracing en Rust, Logback ou Log4j en Java), et les logs partent avec le trace_id et la resource de l’application ;
  • l’application continue d’écrire dans un fichier ou sur la sortie standard, et le Collector les lit avec un receiver comme filelog, avant de les convertir au modèle de données OpenTelemetry.

Dans les deux cas, le Collector applique ses processors puis les exporte vers le backend. Le format de log n’est plus propre à l’outil de stockage.

Deux applications envoient leurs logs au Collector : l’une en OTLP vers le receiver otlp, l’autre via un fichier lu par le receiver filelog. Les deux flux passent par les processors et l’exporter vers le backendApplicationSDK + log bridgeApplicationfichier ou stdoutCollectorreceiverotlpreceiverfilelogprocessorsexporterotlphttpBackendOpenObserveOTLPlu
Avec le log bridge, l’application envoie ses logs en OTLP au receiver otlp. Sans lui, le receiver filelog lit le fichier ou la sortie standard. Les deux flux passent ensuite par les mêmes processors et le même exporter.

Propagation du contexte de trace avec l’en-tête traceparent

Pour que les spans de trois services différents forment une seule trace, il faut propager un contexte d’un service à l’autre. C’est le rôle du standard W3C Trace Context, avec son en-tête HTTP traceparent[4] :

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

Quatre champs séparés par des tirets : la version, le trace-id (16 octets, partagé par toute la trace), le parent-id (8 octets, le span courant) et des flags (ici, échantillonné).

À côté de traceparent, l’en-tête baggage transporte des paires clé-valeur métier (un user.id, un tenant) tout au long de la requête, indépendamment des spans. Baggage est stable dans la spécification OpenTelemetry, pour l’API comme pour le SDK[1].

La propagation relève du transport HTTP (ou gRPC, ou message broker). Un SDK OTel configuré injecte et extrait cet en-tête. Le code applicatif n’a pas à le manipuler.

On verra la mécanique concrète Go → Rust → Java dans l’article sur l’instrumentation des trois services.

Le service Go appelle le service Rust, qui appelle le service Java. Chaque appel transmet l’en-tête traceparent avec le même trace-id et le span_id de l’appelant comme parent-id. Les trois services exportent leurs spans au Collector, qui les relaie au backend sous une seule traceGET /orders (Go)POST /stock (Rust)reserve-stock (Java)traceparent: 00-4bf92f35…4736-00f067aa0ba902b7-01traceparent: 00-4bf92f35…4736-53995c3f42cd8ad8-01export OTLPCollectorpuis backendune seule trace4bf92f35…47363 spans
Chaque service transmet l’en-tête traceparent à l’appel suivant. Le trace-id ne change pas. Le parent-id porte le span_id du span appelant. Les trois spans arrivent séparément au Collector, et le backend les regroupe par trace_id.

L’architecture : API, SDK, instrumentation, Collector, OTLP

OpenTelemetry sépare cinq couches.

L’API est l’ensemble des interfaces que le code applicatif appelle pour créer un span ou incrémenter un compteur. Elle est stable et indépendante de l’implémentation. Le SDK fait le travail concret : échantillonnage, traitement par lots, export.
Sa maturité dépend du langage (voir le tableau plus bas).

L’instrumentation est le code qui produit réellement de la télémétrie à partir de l’application, en zero-code (agent Java, auto-instrumentation) ou à la main (appels explicites à l’API). Le Collector, enfin, est un processus séparé qui reçoit la télémétrie (receivers), la transforme (processors) et la réexpédie (exporters) vers un ou plusieurs backends, selon des pipelines déclarés en configuration.

Le transport entre ces couches, c’est OTLP (OpenTelemetry Protocol), en gRPC (port 4317) ou en HTTP (port 4318). Dans cette série, les applications parleront OTLP/HTTP au Collector, qui relaiera ensuite vers OpenObserve en otlphttp. On détaillera ce montage dans l’article sur l’installation du Collector.

Dans le processus applicatif, le code et son instrumentation appellent l’API, implémentée par le SDK. Le SDK exporte en OTLP/HTTP sur le port 4318 vers le Collector, dont le pipeline receivers, processors, exporters relaie la télémétrie vers OpenObserve en otlphttpProcessus applicatifCode + instrumentationmanuelle ou zero-codeAPISDKéchantillonnage · lots · exportOTLP/HTTP:4318Collectorpipelines déclarés en configurationreceiversprocessorsexportersotlphttpOpenObserve
Le code appelle l’API, implémentée par le SDK. Le SDK exporte en OTLP/HTTP vers le Collector. Son pipeline receivers → processors → exporters relaie la télémétrie vers OpenObserve.

Le processor batch regroupe la télémétrie avant export. Il est en statut bêta et présent dans toutes les distributions du Collector[9]. Une migration vers un batching intégré aux exporters (sending_queue.batch) est en cours, avec un queuebatchprocessor encore en développement[9][10].

Statut de stabilité de la spécification et des SDK

La spécification est stable pour les traces et les logs, ainsi que pour Baggage, le mécanisme de propagation de paires clé-valeur métier décrit plus haut. Pour les métriques, l’API et le protocole sont stables[1]. Le statut des SDK dépend ensuite du langage.

Signal (spec) Statut spec Go Rust Java
Traces Stable Stable Bêta Stable
Métriques API stable, SDK mixed Stable Stable Stable
Logs Stable Stable Stable Stable
Profiles Development — — —

Sources : statut de la spécification[1], notes de version d’OpenTelemetry Go v1.47.0[13] et README d’OpenTelemetry Rust[14], au 8 octobre 2026.

Java et Go sont stables pour les trois signaux. Les logs de Go sont stables depuis la version 1.47.0, publiée le 2 octobre 2026[13]. Rust est stable pour les métriques et les logs, et en bêta pour les traces[14]. La page de statut par langage du site OpenTelemetry[2] indiquait encore, le 8 octobre 2026, une release candidate pour les logs Go et une bêta pour les trois signaux en Rust.
Le choix des versions est détaillé dans l’article sur l’instrumentation des trois services.

Conventions sémantiques et format d’échange

Les conventions sémantiques[5] définissent les noms d’attributs standard, comme http.request.method ou db.system.name.

Elles ne sont pas toutes au même stade :

  • HTTP est stable depuis 2023[6],
  • les bases de données ont un chemin de migration documenté[7],
  • les attributs Kubernetes sont en release candidate[8],
  • et les conventions liées aux agents et aux LLM (GenAI) sont encore en développement actif.

On y reviendra en détail dans le dernier article de la série, consacré à l’observation de Claude Code lui-même.

Ressources

  1. Statut de la spécification OpenTelemetry
  2. Statut par langage
  3. Observability primer
  4. W3C Trace Context
  5. Conventions sémantiques v1.44.0
  6. Conventions HTTP stables depuis 2023
  7. Migration des conventions DB
  8. Conventions Kubernetes en release candidate
  9. README du batch processor
  10. Pourquoi le batch processor va évoluer
  11. Spécification de l’API Trace : TraceId et SpanId
  12. Modèle de données des logs : TraceId et SpanId
  13. OpenTelemetry Go v1.47.0 : première version stable des logs
  14. OpenTelemetry Rust : statut du projet
  15. Trace ID et Span ID dans OpenTelemetry (SigNoz)