Conception

Observabilité : OpenTelemetry vers OpenObserve

Ce qui existe déjà, et pourquoi c'est séparé

Ce dépôt a déjà une pile d'observabilité : Prometheus, Grafana et Blackbox sous ops/observability/, avec des tableaux de bord générés. La pile OpenObserve vit à part, sous infra/analytics/, et n'y touche pas.

Ce n'est pas un doublon assumé mais une comparaison en cours : OpenObserve réunit journaux, métriques et traces dans un seul service là où la pile en place ne couvre que les métriques. La bascule éventuelle se décidera sur mesures, pas sur principe.

Chaîne

Code
Application Node ──OTLP/HTTP──▶ Collecteur OpenTelemetry ──▶ OpenObserve (SDK) :4318 (caviardage, lots) :5080

L'application n'écrit jamais directement dans OpenObserve : le collecteur est le seul à porter les identifiants de stockage, et le seul endroit où un attribut sensible peut encore être retiré.

Mise en route

TypeScriptCode
import { startObservability, configFromEnv } from '@workspace/observability/server'; startObservability(configFromEnv(process.env));

Désactivé tant que OBSERVABILITY_ENABLED et OTEL_EXPORTER_OTLP_ENDPOINT ne sont pas tous deux définis.

Usage

TypeScriptCode
import { log, counter, withSpan } from '@workspace/observability'; const requests = counter('http_requests_total'); await withSpan('structures.list', async () => { requests(1, { route: '/structures' }); log('info', 'liste des structures rendue', { count: 248 }); });

Les trois chemins caviardent leurs attributs. log() ajoute traceId et spanId pour corréler journal et trace — ce sont des identifiants de corrélation, jamais des identifiants de personne.

Piège corrigé : les journaux ne partaient pas

NodeSDK n'enregistre pas le fournisseur de journaux global, même avec l'option logRecordProcessors. logs.getLogger() renvoyait alors un ProxyLogger inerte : aucune erreur, aucun journal. Vérifié avec @opentelemetry/sdk-node 0.300 le 25/10/2026.

startObservability construit donc un LoggerProvider et appelle explicitement logs.setGlobalLoggerProvider(). Le symptôme à surveiller si la régression revient : le logger retourné est un ProxyLogger et non un Logger.

Où atterrissent les données

SignalFlux OpenObserve
Journauxlogs / default
Tracestraces / default — pas le nom du span
Métriquesmetrics / un flux par nom de métrique

Interroger le flux nommé d'après le span renvoie un résultat vide : c'est l'erreur commise lors de la mise au point du test de fumée.

Interroger

TerminalCode
AUTH=$(grep '^OPENOBSERVE_AUTH=' infra/analytics/compose/.env | cut -d= -f2-) END=$(( ($(date +%s) + 60) * 1000000 )); START=$(( END - 3600000000 )) curl -s -X POST -H "Authorization: $AUTH" -H 'Content-Type: application/json' \ "http://localhost:5080/api/default/_search?type=logs" \ -d "{\"query\":{\"sql\":\"SELECT * FROM \\\"default\\\" LIMIT 10\",\"start_time\":$START,\"end_time\":$END,\"size\":10}}"

Les bornes temporelles sont en microsecondes.

Export différé

Journaux et traces partent par lots : interroger immédiatement après l'émission renvoie un résultat vide sans que rien ne soit cassé. Le test de fumée interroge en boucle jusqu'à 20 secondes. Toute vérification manuelle doit faire de même.

Ce qui n'est pas fait

  • Aucun tableau de bord OpenObserve n'est provisionné : les données sont interrogeables, les tableaux de bord restent à construire.
  • Aucune alerte n'est définie.
  • Pas de TLS entre l'application et le collecteur : acceptable en local, à reprendre pour un déploiement distant.
  • L'instrumentation automatique (auto-instrumentations-node) est installée mais pas activée : elle n'a pas été évaluée sur les requêtes réelles de l'application.