Exploitation

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 :

    TerminalCode
    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 :

TypeScriptCode
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 :

TerminalCode
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.

TerminalCode
# 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 :

TerminalCode
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 :

    TerminalCode
    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 :

TerminalCode
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éé :

TerminalCode
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.

TerminalCode
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.