# 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

```text
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

```ts
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

```ts
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

| Signal    | Flux OpenObserve                              |
| --------- | --------------------------------------------- |
| Journaux  | `logs` / `default`                            |
| Traces    | `traces` / **`default`** — pas le nom du span |
| Métriques | `metrics` / 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

```bash
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.
