pour les applications React Native
Vous ne pouvez pas lire les journaux sur le téléphone de quelqu’un d’autre.
Une application mobile est une couche de rendu au-dessus d’une API, donc presque tout ce qui casse s’y voit en premier — et la forme de ce qui revient compte ici plus que partout ailleurs, parce qu’un point d’entrée qui renvoie dix fois plus de lignes coûte à un serveur un peu de mémoire et coûte à un téléphone sa batterie, son forfait de données et, à la fin, son processus. Sixty mesure cela depuis l’intérieur de l’application, compte les plantages contre les lancements plutôt qu’isolément, et compare chaque version à la précédente.
Six façons de casser sans que rien ne lève d’exception
Aucune de ces situations ne lève d’erreur, ne fait échouer une requête, ni ne déplace assez un temps de réponse pour déclencher une alerte. C’est ce que voient vos utilisateurs. Choisissez-en une.
silent_emptyIt loaded, and there was nothing there
The call succeeded. The server answered 200, quickly, and came back with nothing — so every server-side metric records this as a healthy request, and the user is looking at a screen with nothing on it.
The finding names the endpoint and the release the emptiness started in. A response that went from twelve rows to zero without an error is a filter or a policy that changed, not an outage — which is why nothing paged anybody.
Ce qu’il mesure
- Chaque requête que fait l’application
- Combien de temps elle a pris, ce qui est revenu, quelle taille cela faisait et combien de lignes cela portait — instrumenté au niveau de XHR, là où `fetch` et tout client HTTP au-dessus finissent.
- Les plantages, à part des erreurs
- Une exception non attrapée sur un serveur perd une requête. Sur un téléphone elle ferme l’application pendant que quelqu’un la tient. Ce ne sont pas des degrés d’une même mesure, ils ne sont donc pas moyennés ensemble — et `app_starts` est enregistré comme dénominateur, parce que « quatorze plantages » n’est pas un chiffre sur lequel qui que ce soit puisse agir.
- Requêtes par écran, et appels par rendu
- Le plugin Babel fait des composants des spans parents, ce qui transforme « dix-sept requêtes » en « cet écran fait dix-sept requêtes, et en faisait trois à la version précédente ».
- Sur quel écran chaque chose s’est produite
- Un traqueur de navigation nomme l’écran courant, donc un constat dit où et pas seulement quoi. React Navigation se branche avec deux props.
- Des fenêtres qui survivent à la session
- Passer en arrière-plan est la façon dont se terminent la plupart des sessions mobiles, et rien sur iOS ni Android ne terminera une requête pour une application qui ne tourne plus. Les fenêtres sont écrites dans le stockage avant d’être envoyées et effacées seulement sur un 2xx confirmé, donc la dernière avant que quelqu’un range son téléphone arrive au lancement suivant — sous la version qui l’a produite, pas celle installée maintenant.
Ce qu’il trouve
Chacune de ces choses est comparée à la version précédente : le constat nomme donc le changement plutôt que le jour.
Un écran qui s’est mis à faire dix-sept requêtes
Il en faisait trois à la version précédente. Chacune est rapide, donc aucune requête prise seule n’a l’air fausse et aucune mesure de temps ne bouge assez pour se remarquer — seul le nombre est faux, et en données mobiles c’est le nombre que l’utilisateur ressent.
Une boucle de rendu, vue de l’extérieur
Une fonction appelée bien plus souvent qu’avant, inchangée par appel. C’est à cela que ressemble un effet à dépendance instable quand on ne peut pas voir le source : pas de plantage, pas de requête échouée, et une batterie qui se vide en un après-midi.
Un point d’entrée qui s’est mis à tout renvoyer
Le même appel, dix fois plus de lignes. Sur un serveur, c’est un peu de mémoire et quelques millisecondes. Sur un appareil, c’est le forfait de données de l’utilisateur et, à la fin, le système qui tue une application qui a grossi plutôt que de la laisser faire.
Des appels qui réussissent et reviennent vides
Un 200 sans rien dedans. L’écran s’affiche vide, rien ne lève d’exception, et toutes les métriques côté serveur remontent cela comme sain.
Des sessions refusées
Une part des appels vers un point d’entrée répondue en 401 ou 403. Un jeton qui a discrètement cessé de se renouveler ressemble exactement à cela, pendant des heures, avant le premier message au support.
L’application qui se ferme pendant que quelqu’un s’en sert
Erreurs JavaScript fatales comptées contre les lancements, donc le chiffre est un taux plutôt qu’un décompte — et groupées par l’endroit du code d’où elles viennent plutôt que par leur message.
Des erreurs qui n’atteignent jamais un rapporteur de plantages
Celles qu’une barrière attrape : pas de plantage, une zone vide là où devrait être un écran, et un utilisateur qui revient en arrière et réessaie.
Comment ça s’installe
Trois lignes, et un plugin Babel qui en fait une de plus. Metro, c’est Babel, donc la même transformation qu’utilise l’agent Node fonctionne ici sans changement — c’est elle qui apporte les requêtes par écran et le signal de boucle de rendu, et les règles statiques viennent avec.
npm install @sixty-sh/react-native
// index.js — before anything else
import { init, composeRelease } from '@sixty-sh/react-native'
init({
key: 'sixty_pk_…',
service: 'my-app',
// A native version alone reports every OTA bundle you push as the
// same release, which is the one thing a comparison cannot survive.
release: composeRelease({ version: '1.4.2', otaUpdateId }),
})
// babel.config.js
module.exports = { plugins: ['@sixty-sh/react-native/babel'] }Installez aussi @react-native-async-storage/async-storage et la file de sortie durable fonctionne toute seule — sans lui l’agent tourne toujours et perd les fenêtres qui étaient dues quand l’application est passée en arrière-plan. Pour les noms d’écran, passez les deux gestionnaires du traqueur de navigation à votre NavigationContainer.
Ce qu’il ne fait pas
- Pas de temps de lancement, pas d’images perdues, pas de saccades. C’est l’équivalent mobile des web vitals et cela ne peut pas être mesuré depuis JavaScript — il faut un module natif lisant CADisplayLink ou MetricKit sur iOS et JankStats sur Android. Mesurer la fréquence d’images depuis le thread JS remonterait l’opinion du thread JS à ce sujet, c’est-à-dire précisément le chiffre qui est faux quand l’application saccade.
- Les plantages natifs sont absents. Un mauvais pointeur dans un module natif n’atteint jamais JavaScript, donc ce qui est compté ici est une erreur *JavaScript* fatale.
- Les builds de production groupent bien et localisent mal. Hermes compile le bundle en bytecode, donc une image de pile arrive comme un décalage dans index.android.bundle plutôt que comme un fichier de votre dépôt. Cela reste une identité stable pour un bug — la propriété qui compte le plus — mais cela ne pointera pas vers une ligne de votre code tant que l’envoi de sourcemaps n’existera pas. Un build de développement a de vrais chemins.
- La comparaison entre versions n’est pas par cohortes, et c’est ici le plus gros point ouvert. Un déploiement serveur remplace ce qui tournait ; une version mobile non. Elle se déploie sur plusieurs jours, les anciennes versions tournent indéfiniment, et les utilisateurs qui mettent à jour en premier ont plutôt des appareils plus récents et de meilleures connexions — donc A contre B est en partie une comparaison contre une population qui se déplace.
- Il n’y a pas de proxy, donc pas de confidentialité d’IP structurelle. Le meilleur mode de l’agent navigateur ne détient aucune information d’identification et poste vers votre propre origine, ce qui fait qu’aucune requête d’un visiteur n’atteint jamais le collecteur. Une application n’a pas d’origine à elle, donc cet arrangement est simplement indisponible et cet agent porte une clé publique. Si vous exploitez déjà une API, pointez `endpoint` dessus et relayez depuis là, et la propriété est exactement restaurée.
Si vous êtes ici pour l’une de ces raisons
Découvrez ce qu’a fait votre dernier changement.
Essayez !Vous construisez autre chose ? Voyez services Node ou applications Lovable — ces pages sont différentes, et ce que nous voyons aussi.