Le problème : une exception ne fait pas tomber le site, elle fait abandonner un visiteur. Erreurs regroupe les exceptions du navigateur et du serveur, puis les classe par visiteurs touchés qui n'ont rien fini ensuite — pas par nombre d'occurrences. Une exception vue dix mille fois par un robot vaut moins qu'une exception vue douze fois sur /paiement par douze personnes qui n'ont pas payé.
Autour de ce classement, le module tient l'atelier : un état par erreur, un responsable, une mise en sourdine, des notes d'équipe, la version du logiciel où l'erreur est apparue, et une API qui fait tout ce que l'écran fait.
Démarrer en 30 secondes
Dans le navigateur — la balise Stats, avec un attribut de plus. Elle envoie les exceptions non rattrapées et les promesses rejetées, au plus cinq fois la même par page.
``html <script defer src="https://letock.fr/t.js" data-site="CLE_DU_DOMAINE" data-erreurs></script> ``
Côté serveur — une requête depuis un gestionnaire d'exception, avec une clé en écriture. version est facultatif et change tout : c'est lui qui désigne le déploiement fautif.
``bash curl -X POST https://letock.fr/api/v1/erreurs \ -H "Authorization: Bearer tock_…" \ -H "Content-Type: application/json" \ -d '{"site":"CLE_DU_DOMAINE","message":"TypeError: prix de undefined","pile":"at f (/app/panier.js:412:18)","chemin":"/api/commande","version":"2.4.1","fil":["POST /panier 200","POST /commande 500"]}' ``
Réponse : 202 et {"enregistre":true,"groupe":"<uuid>","nouveau":true,"etat":"ouvert"}. Rien d'autre à déclarer : le groupe se crée tout seul, et l'écran /erreurs le montre immédiatement.
Le regroupement, et ce qu'il implique
L'empreinte d'un groupe est calculée sur le message normalisé et le fichier d'origine, pas sur le texte brut : nombres, identifiants hexadécimaux, chaînes citées et URL deviennent des jokers, et le fichier perd son empreinte de build (/assets/app-3f2a1b9c.js:412:18 devient /assets/app.js). Sans cette normalisation, une erreur portant un numéro de commande créerait un groupe par commande.
Deux conséquences à connaître :
- deux messages qui ne diffèrent que par des nombres ou des identifiants fusionnent. C'est presque toujours voulu, parfois surprenant ;
- la même erreur levée depuis deux fichiers différents reste deux groupes. La fiche les rapproche (voir « erreurs voisines ») plutôt que de les fondre.
Un groupe appartient à un domaine, donc à un projet. « Script error. » — ce que dit un navigateur d'une exception venue d'un script d'un autre domaine — est conservé et nommé explicitement : c'est presque toujours une extension ou un tag publicitaire.
Les quatre états
| état | ce qu'il promet |
|---|---|
ouvert | comptée, classée par impact, alertée quand elle touche du monde. État de naissance |
en_cours | quelqu'un s'en occupe. Reste comptée et alertée : « on regarde » n'a jamais réparé personne |
resolu | sort des comptes. Une occurrence postérieure rouvre le groupe et déclenche l'alerte de régression |
ignore | sort des comptes pour de bon. Aucune alerte, jamais de réouverture |
La sourdine est une cinquième possibilité, et ce n'est pas un état : elle arrête le courrier pendant *n* heures (au plus 720) sans sortir l'erreur des comptes ni de l'écran. C'est le geste qui convient quand un correctif est en cours de déploiement ; ignore est le geste qui convient pour l'extension de navigateur d'un visiteur.
Confier une erreur ouverte à quelqu'un la passe automatiquement en_cours : c'est exactement l'information qu'on cherchait à donner.
L'impact métier
Pour chaque groupe, sur sept jours glissants : le nombre de visiteurs distincts touchés, et parmi eux ceux qui ont quand même fini — un événement métier Stats (tout sauf une page vue) émis après l'erreur, le même jour. Les autres ont abandonné. C'est la différence touchés − ont fini qui commande le tri par défaut.
Le rapprochement passe par l'empreinte de visiteur de Stats, calculée avec le même sel du jour : il est possible dans la journée, impossible le lendemain. C'est voulu, et c'est ce qui permet de mesurer sans jamais identifier personne.
Si le domaine n'émet aucun événement métier, l'écran le dit au lieu d'afficher un zéro : « impact inconnu ». Un zéro aurait été un mensonge.
Suivi par version
Quand version accompagne une erreur, Tock retient la version de la première et de la dernière occurrence du groupe, et tient un compte par version. La fiche répond alors à la question qui ferme le dossier : quel déploiement l'a introduite, et lequel ne l'a pas fait disparaître.
Le compteur par version survit à la purge des occurrences : il est tenu au fil de l'eau, pas recalculé.
Les alertes
Deux moments, et pas plus.
- Une erreur nouvelle qui touche du monde : trois visiteurs distincts sur sept jours, ou dix occurrences côté serveur. Une alerte, une seule, jamais à la première occurrence — un visiteur avec une extension cassée n'est pas une panne.
- Une erreur résolue qui revient : dès la première occurrence, parce qu'on savait déjà qu'elle comptait. C'est l'alerte la plus utile du module.
Aucun résumé quotidien en plus : l'écran classe par impact, le courrier dit ce qui est nouveau. La sourdine et l'état ignore éteignent l'un comme l'autre le courrier ; seul ignore éteint aussi la pastille du module.
Chaque alerte dépose en plus un signal dans le journal de la plateforme, ce qui permet à Signaux de rapprocher une erreur d'un déploiement ou d'une chute de conversion.
Les routes d'API
Toutes les routes /api/v1 s'authentifient par Authorization: Bearer tock_…. Les lectures exigent la mission « lire » ; l'écriture d'erreurs exige la mission « rapporter des erreurs » (erreurs.ecrire) et une clé en écriture. 300 appels par minute et par clé.
POST /api/erreurs — la route publique de la balise
Sans authentification, appelée par le navigateur des visiteurs. Corps JSON abrégé : s (clé publique du domaine), m (message), t (pile), f (fichier), p (chemin), v (version), c (contexte, objet). Répond toujours `204`, quoi qu'il arrive : une clé fausse, un quota atteint, une limite dépassée donnent le même silence. C'est volontaire — cette route est appelée par les visiteurs de nos clients, elle ne doit rien révéler et rien coûter. Au-delà de 300 requêtes par minute et par domaine, le reste est jeté : une boucle d'erreur sur une page en envoie des milliers.
Corollaire : on ne diagnostique jamais une intégration de balise par cette route. On vérifie sur l'écran, ou avec la route authentifiée.
La route accepte v et c, mais la balise ne les envoie pas encore : côté navigateur, la version et le contexte ne remontent que si vous appelez la route vous-même. Côté serveur, version, contexte et fil fonctionnent dès aujourd'hui.
POST /api/v1/erreurs — signaler une erreur serveur
| champ | type | obligatoire |
|---|---|---|
site | clé publique du domaine | oui |
message | texte | oui |
pile | texte, 4 000 caractères | non |
chemin | texte, 500 caractères | non |
version | texte, 60 caractères | non |
contexte | objet libre, 4 000 caractères une fois sérialisé | non |
fil | tableau des derniers gestes avant l'erreur, 20 entrées | non |
202 → { "enregistre": true, "groupe": "<uuid>", "nouveau": bool, "etat": "<état>" }.
Erreurs : 400 champs_manquants, 400 corps_invalide, 400 refus_metier (message vide), 401 cle_absente / cle_invalide, 403 lecture_seule / mission_non_autorisee, 404 site_inconnu, 429 quota_atteint.
GET /api/v1/erreurs — chercher
Paramètres, tous facultatifs : projet (sinon le projet par défaut), site (une clé de domaine, qui restreint à ce domaine et fixe le projet), etat (une ou plusieurs valeurs séparées par des virgules ; défaut ouvert,en_cours), cote (navigateur ou serveur), q (recherche libre), version, tri (impact par défaut, frequence, recence, nouveaute), limite (200 au plus), depuis_rang (décalage de pagination), format (json, csv, ndjson).
200 → { "erreurs": [ … ], "total": n, "projet": "<uuid>" }. En csv et ndjson, le corps est le fichier lui-même, sans enveloppe.
Chaque erreur porte : id, titre, etat, cote, domaine, fichier, premiere_vue_le, derniere_vue_le, total, occurrences_7j, visiteurs_touches, visiteurs_qui_ont_fini, abandons, pages, assigne_a, sourdine_jusqu_au, premiere_version, derniere_version, notes, serie_14j.
La recherche q porte sur trois choses : le titre, le message normalisé (donc sans les identifiants qui changent à chaque occurrence) et le fichier d'origine. Chercher commande # introuvable trouve les erreurs dont le titre contient un numéro de commande.
Erreurs : 422 champ_etat / champ_cote / champ_tri / champ_format, 404 site_inconnu / projet_inconnu.
GET /api/v1/erreurs/{id} — la fiche
200 → { erreur, versions, pages, occurrences, notes, similaires }. erreur reprend les champs ci-dessus, plus pile et impact_mesurable (le domaine émet-il des événements métier). occurrences donne les douze dernières avec fil et contexte. similaires donne les groupes voisins avec leur raison : fichier ou message.
404 introuvable si le groupe n'est pas à cette équipe — même réponse que s'il n'existait pas, et c'est délibéré.
PATCH /api/v1/erreurs/{id} — travailler l'erreur
Corps : au moins un de etat, assigne_a, sourdine_h, note.
``bash curl -X PATCH https://letock.fr/api/v1/erreurs/ID -H "Authorization: Bearer tock_…" \ -H "Content-Type: application/json" \ -d '{"etat":"resolu","note":"corrigé en 2.4.2, déployé mardi"}' ``
assigne_a prend l'adresse e-mail d'un membre de l'équipe, ou null — personne ne connaît par cœur l'identifiant de son collègue. sourdine_h prend un nombre d'heures (720 au plus) ou null pour rétablir les alertes. note ajoute une note d'équipe ; posée par une clé d'API, elle n'a pas d'auteur et l'écran l'affiche ainsi.
200 → { "erreur": { … } } avec l'état après coup. Erreurs : 422 corps_vide / champ_etat / champ_assigne_a / champ_sourdine_h / champ_note, 404 introuvable / membre_inconnu.
Il n'y a pas de `DELETE`. Supprimer un groupe et son historique tomberait aujourd'hui dans la mission « rapporter des erreurs », donc dans les mains de tout agent qui remonte ses exceptions. L'oubli définitif reste un geste d'humain connecté, sur la fiche ; passer le groupe en ignore couvre le besoin réel sans rien détruire.
Les écrans
`/erreurs` — la liste. Cinq onglets, chacun à son adresse : *À traiter* (ouvert et en_cours, le défaut), *Pour moi*, *Résolues*, *Ignorées*, *Toutes*. Une barre de filtres — recherche, côté, version, tri — et une pagination. Chaque ligne montre l'impact, la frise de quatorze jours, l'état, le responsable, la version d'apparition, et trois gestes rapides : marquer résolue, prendre l'erreur, ignorer. Tout l'état de l'écran est dans l'URL : un onglet, une recherche et une page s'envoient par message.
`/erreurs/{id}` — la fiche. L'impact et les chiffres, les états et leurs promesses, l'assignation, la sourdine, le tableau par version, la pile d'un exemple, les pages touchées, les douze dernières occurrences avec leur fil d'Ariane et leur contexte, les notes d'équipe, les erreurs voisines, les appels d'API prêts à copier, et le retrait définitif derrière une confirmation.
Quotas
Une occurrence enregistrée consomme une unité du poste `erreurs` du forfait de l'équipe (les valeurs sont dans src/plateforme/forfaits.ts ; la page « Mon compte » les montre). Au-delà, la route authentifiée répond 429 quota_atteint et la route publique se tait. Une erreur refusée par quota n'est pas comptée : marteler une API déjà refusée ne gonfle pas le compteur.
Le regroupement, l'assignation, les notes et les changements d'état ne consomment rien.
Les pièges
- La route publique ne dira jamais qu'elle refuse. Toujours
204. Pour diagnostiquer une intégration, passer par l'écran ou la route authentifiée. - `resolu_le` vaut pour « résolu » comme pour « ignoré » : c'est la date à laquelle le groupe a cessé de compter. Seul
resoluse rouvre à l'occurrence suivante. - La sourdine n'est pas « ignoré » : elle arrête le courrier, pas le comptage ni l'affichage.
- « Qui a fini » exige des événements métier Stats sur le même domaine, le même jour. Sans eux, l'impact est affiché comme inconnu, et le tri retombe sur les visiteurs touchés.
- Les occurrences sont purgées à trente jours, et un groupe sans occurrence depuis trente jours disparaît avec ses notes et ses versions. L'écran ne prévient pas.
- Il n'y a pas de source maps. Une pile minifiée reste minifiée : Tock ne stocke pas vos fichiers de correspondance et ne les applique pas. Le fichier d'origine est débarrassé de son empreinte de build, ce qui suffit au regroupement, pas à la lecture d'une pile. Envoyez
versionet une pile non minifiée côté serveur. - Le tri par impact peut mettre en tête une erreur à faible volume. C'est le propos : trois personnes bloquées au paiement comptent plus que mille robots.
- Une erreur résolue qui revient ne réalerte qu'une fois. L'alerte de régression est posée, puis le groupe redevient un groupe ouvert ordinaire.