# Dépannage

Tous les cas ci-dessous ont été **rencontrés** pendant la mise au point du POC, pas imaginés.

## « L'événement part, le fournisseur répond 200, rien n'arrive »

Le plus fréquent. Dans l'ordre :

1. **User-Agent.** Umami et Rybbit filtrent les robots et **répondent succès** en jetant
   l'événement. Umami n'écrit rien ; Rybbit range dans `bot_events`. Un User-Agent contenant
   `+http` suffit à déclencher le filtre. Vérifier :

   ```bash
   docker logs compose_rybbit-backend_1 2>&1 | grep botDetectionTotals | tail -1
   ```

2. **Export différé.** Journaux et traces partent par lots : interroger immédiatement renvoie un
   résultat vide. Attendre et réinterroger, jusqu'à 20 secondes.

3. **Mauvais flux.** Les traces OpenObserve sont dans `traces/default`, **pas** sous le nom du span.

## Aucun journal dans OpenObserve, aucune erreur

`NodeSDK` n'enregistre pas le fournisseur de journaux global. Diagnostic :

```ts
import { logs } from '@opentelemetry/api-logs';
console.log(logs.getLogger('probe').constructor.name);
```

`ProxyLogger` signale le problème, `Logger` que tout va bien. `startObservability` appelle
`logs.setGlobalLoggerProvider()` : si la régression revient, c'est là qu'il faut chercher.

Pour isoler le SDK du reste de la chaîne, envoyer un journal OTLP brut au collecteur :

```bash
NOW_NS=$(( $(date +%s) * 1000000000 ))
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:4318/v1/logs \
  -H 'Content-Type: application/json' \
  -d "{\"resourceLogs\":[{\"resource\":{\"attributes\":[{\"key\":\"service.name\",\"value\":{\"stringValue\":\"probe\"}}]},\"scopeLogs\":[{\"logRecords\":[{\"timeUnixNano\":\"$NOW_NS\",\"severityText\":\"INFO\",\"body\":{\"stringValue\":\"probe\"}}]}]}]}"
```

Si ce journal arrive et pas celui du SDK, le problème est dans le SDK, pas dans le collecteur.

## « Illegal invocation » : les événements partent, rien ne sort

Symptôme : `window.__analytics.diagnostics()` contient
`vue de page non envoyée : Failed to execute 'fetch' on 'Window': Illegal invocation`,
et aucune requête réseau n'est émise.

Cause : `fetch` stocké en champ de classe puis appelé par `this.fetchImpl(...)` reçoit l'instance
de l'adaptateur comme receveur. Le navigateur refuse. Les adaptateurs détachent donc la référence
avant l'appel (`const send = this.fetchImpl; await send(...)`).

Attention au diagnostic : appeler `globalThis.fetch` déréférencé **en fonction nue** fonctionne,
c'est l'appel **en méthode** qui échoue. Un test qui ne reproduit pas la forme d'appel réelle
conclut à tort que tout va bien. Un test de non-régression couvre ce cas
(`tests/unit/analytics/core.test.ts`).

## Le composant d'analytics ne se monte pas du tout

Symptôme : aucune requête vers les fournisseurs, aucune erreur visible, et `window.__analytics`
absent alors que `NEXT_PUBLIC_ANALYTICS_DEBUG=true`.

Vérifier d'abord que **l'hydratation n'est pas cassée** : un seul chunk JavaScript en échec suffit
à interrompre toute la page, et les composants montés après ne s'exécutent jamais.

```bash
# Chunks référencés par la page mais absents du build
curl -s http://localhost:3005/ | grep -oE '/_next/static/chunks/[A-Za-z0-9_.-]+\.js' \
  | sed 's|.*/||' | sort -u \
  | while read -r f; do [ -f ".next-saclay/static/chunks/$f" ] || echo "manquant : $f"; done
```

Rencontré pendant la mise au point : un chunk référencé par le HTML mais **absent de la sortie de
build**, servi en HTTP 500 avec un type MIME `text/plain`. La console du navigateur affiche alors
« Refused to execute script … MIME type is not executable ».

Cause probable : des reconstructions successives dans le même dossier `.next-<instance>` laissant
un cache incohérent. Correctif :

```bash
rm -rf .next-saclay && pnpm build:saclay
```

Nettoyer le dossier de build avant une reconstruction après changement de variables
`NEXT_PUBLIC_*` évite ce cas.

## Les requêtes partent en 200, les tableaux de bord restent vides

Symptôme : le test de bout en bout voit des requêtes d'ingestion acceptées, et les fournisseurs ne
comptent rien.

Deux causes, souvent confondues :

1. **Le navigateur de test est pris pour un robot.** Le User-Agent par défaut de Chromium sans
   interface contient `HeadlessChrome`. Umami répond alors `200` avec le corps `{"beep":"boop"}` et
   ne stocke rien ; Rybbit range l'événement dans `bot_events`. Vérification directe :

   ```bash
   curl -s -X POST http://localhost:3001/api/send -H 'Content-Type: application/json' \
     -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) HeadlessChrome/141.0.0.0' \
     -d '{"type":"event","payload":{"website":"<id>","url":"/essai"}}'
   # {"beep":"boop"}  → filtré. Une réponse contenant "cache"/"sessionId" signifie accepté.
   ```

   `smoke-app.ts` remplace `HeadlessChrome` par `Chrome` dans le contexte Playwright.

2. **La lecture de vérification est fausse.** `GET /api/sites/{id}/events/count` de Rybbit renvoie
   un tableau de tranches horaires, pas un total : il faut sommer `pageview_count`. Lu comme un
   nombre, il vaut zéro en permanence, sans erreur.

Règle générale : un test qui conclut « rien n'est arrivé » doit d'abord être mis en doute
lui-même, en interrogeant le fournisseur à la main.

## La recherche affiche « index non construit » alors que l'index existe

Symptôme : `/pagefind/<instance>/pagefind.js` répond 404, mais le fichier est bien là sur le
disque. Redémarrer le serveur suffit à tout remettre en ordre.

Cause : `next start` fige la liste des fichiers de `public/` au démarrage. Or `pnpm build:<instance>`
**supprime puis réécrit** `public/pagefind/<instance>/` après la construction des pages, et
l'écriture de 110 Mo prend une bonne minute. Un serveur démarré pendant cette fenêtre sert des
404 jusqu'à son redémarrage — et la page de recherche affiche un message qui accuse le build.

Attendre la fin complète du script, pas la ligne « Indexed … pages » de Pagefind : elle précède
l'écriture.

## Un événement métier manque, les vues de page sont là

Les vues de page sont vidées **à l'enfilement** ; les autres événements attendent l'échéance de
la file, **5 s par défaut** (`flushIntervalMs`). Un événement émis juste avant une navigation
part donc au vidage déclenché par le masquage de l'onglet, pas avant.

Conséquence pour un test : fermer le navigateur moins de 5 s après le geste mesure l'empressement
du test, pas la chaîne. `smoke-app.ts` laisse passer l'échéance.

Conséquence pour la production : sur un site à chargements complets, ce vidage de dernière chance
repose sur `visibilitychange` et sur `keepalive`. C'est une livraison « au mieux », assumée.

## Matomo répond « Unable to authenticate with the provided token »

Le jeton d'API de `infra/analytics/compose/.env` a expiré. En régénérer un :

```bash
curl -s -X POST http://localhost:8081/index.php \
  --data-urlencode module=API --data-urlencode format=json \
  --data-urlencode method=UsersManager.createAppSpecificTokenAuth \
  --data-urlencode userLogin=admin --data-urlencode passwordConfirmation='<mot de passe>' \
  --data-urlencode description=smoke
```

puis remplacer `MATOMO_TOKEN_AUTH`. Le symptôme trompe : le test de fumée annonce « aucun
événement retrouvé », alors que les événements sont bien stockés.

## Matomo reste sur l'écran d'installation

`core:install` n'existe pas en Matomo 5. Lancer `node infra/analytics/scripts/provision-matomo.mjs`.
Vérifier que le schéma est créé :

```bash
docker exec compose_matomo-db_1 mariadb -umatomo -pmatomo-local matomo -N \
  -e "select count(*) from information_schema.tables where table_schema='matomo';"
```

31 tables attendues. Le client en ligne de commande s'appelle `mariadb`, pas `mysql`.

## L'API Rybbit répond MISSING_OR_NULL_ORIGIN

better-auth exige un en-tête `Origin` **et** un cookie de session. Un jeton Bearer ne suffit pas
pour l'API d'administration — il ne sert que pour `/api/track`.

## Le compose ne se valide pas

Cette machine n'a que `docker-compose` v1.29. Les fichiers évitent la syntaxe propre à v2.
`node infra/analytics/scripts/stack.mjs config` détecte la version disponible.

## Rien ne démarre

C'est voulu : aucun profil, aucun service. Préciser un profil.

```bash
node infra/analytics/scripts/stack.mjs up openobserve umami matomo rybbit
```

## Le test de fumée dit « ignoré »

Service éteint ou identifiant absent de `compose/.env`. Lancer le script de provisionnement
correspondant. « Ignoré » ne fait pas échouer le test : c'est une capacité non vérifiée, pas un
échec.
