Fermer

mai 2, 2018

Capturez et signalez les erreurs JavaScript avec window.onerror –


Cet article a été créé en partenariat avec Sentry . Merci de soutenir les partenaires qui rendent SitePoint possible.

onerror est un événement spécial du navigateur qui se déclenche chaque fois qu'une erreur JavaScript non récupérée a été lancée. C'est l'un des moyens les plus faciles de consigner les erreurs côté client et de les signaler à vos serveurs. C'est aussi l'un des principaux mécanismes par lesquels fonctionne l'intégration JavaScript de Sentry (Raven-js)

Vous écoutez l'événement onerror en attribuant une fonction à window.onerror :

 window.onerror = fonction (msg, url, lineNo, columnNo, erreur) {
  // ... gérer l'erreur ...

  return false;
}

Lorsqu'une erreur est renvoyée, les arguments suivants sont passés à la fonction:

  • msg – Le message associé à l'erreur, p. "Unrefried ReferenceError: foo n'est pas défini"
  • url – L'URL du script ou document associé à l'erreur, par ex. "/dist/app.js"
  • lineNo – Le numéro de ligne (si disponible)
  • columnNo – Le numéro de colonne (si disponible)
  • erreur – L'objet Error associé avec cette erreur (si disponible)

Les quatre premiers arguments vous indiquent dans quel script, ligne et colonne l'erreur s'est produite. L'argument final, objet d'erreur, est peut-être le plus précieux. Apprenons pourquoi.

L'objet d'erreur et error.stack

À première vue, l'objet Error n'est pas très spécial. Il contient 3 propriétés standardisées: message nomFichier et numéro de ligne . Les valeurs redondantes qui vous sont déjà fournies via window.onerror .

La partie précieuse est une propriété non standard : Error.prototype.stack . Cette propriété de pile vous indique à quel emplacement source chaque image du programme était lorsque l'erreur s'est produite. La trace de pile d'erreurs peut être une partie critique du débogage. Et bien qu'étant non standard, cette propriété est disponible dans tous les navigateurs modernes.

Voici un exemple de la propriété stack de l'objet Error dans Chrome 46:

 "Erreur: foobar  n à la nouvelle barre (: 241 : 11)  n à foo (: 245: 5)  n à : 250: 5  n à : 251: 3  n à : 267: 4  n à callFunction (: 229: 33)  n à : 239: 23  n à : 240: 3 à Object.InjectedScript._evaluateOn (: 875: 140)  n at Object .InjectedScript._evaluateAndWrap (: 808: 34) "

Difficile à lire, non? La propriété stack est en fait juste une chaîne non formatée.

Voici à quoi ça ressemble:

 Erreur: foobar
    au nouveau bar (: 241: 11)
    à foo (: 245: 5)
    à callFunction (: 229: 33)
    à Object.InjectedScript._evaluateOn (: 875: 140)
    à Object.InjectedScript._evaluateAndWrap (: 808: 34)

Une fois formaté, il est facile de voir comment la propriété stack peut être critique pour aider à déboguer une erreur.

Il y a juste un hic: la propriété stack n'est pas standard, et son implémentation diffère d'un navigateur à l'autre. Par exemple, voici la même trace de pile à partir d'Internet Explorer 11:

 Erreur: foobar
   en bar (code de script inconnu: 2: 5)
   at foo (Code de script inconnu: 6: 5)
   à la fonction Anonyme (code de script inconnu: 11: 5)
   à la fonction anonyme (code de script inconnu: 10: 2)
   à la fonction Anonymous (code de script inconnu: 1: 73)

Non seulement le format de chaque image est différent, mais les images ont aussi moins de détails. Par exemple, Chrome identifie que le nouveau mot-clé a été utilisé et a un meilleur aperçu des invocations eval . Et ce n'est que IE 11 par rapport à Chrome – d'autres navigateurs ont des formats et des détails différents.

Heureusement, il existe des outils qui normalisent les propriétés de la pile afin qu'elles soient cohérentes entre les navigateurs. Par exemple, Raven-js utilise TraceKit pour normaliser les chaînes d'erreur. Il y a aussi stacktrace.js et quelques autres projets

Browser Compatibility

window.onerror est disponible dans les navigateurs depuis quelque temps – vous le trouverez dans des navigateurs aussi anciens que IE6 et Firefox 2.

Le problème est que chaque navigateur implémente window.onerror différemment, en particulier, dans combien d'arguments sont envoyés à l'écouteur onerror et à la structure de ces arguments.

Voici un tableau dont les arguments sont passés à onerror dans la plupart des navigateurs:

Navigateur Message URL ligneNo colNo erreurObj
Firefox ✓ [19659036] ✓ Chrome
Bord
IE 11
IE 10 ✓ [19659036] ✓
IE 9, 8
Safari 10 et plus ✓ [19659041]
Navigateur android 4.4

C'est sans doute pas une surprise Internet Explorer 8, 9 et 10 ont un support limité pour onerror. Mais vous pourriez être surpris que Safari n'a ajouté que le support de l'objet d'erreur dans Safari 10 (sorti en 2016). De plus, les anciens combinés mobiles qui utilisent encore le navigateur Android stock (maintenant remplacé par Chrome Mobile) sont toujours là et ne transmettent pas l'objet d'erreur.

Sans l'objet d'erreur, il n'y a pas de propriété de trace de pile. Cela signifie que ces navigateurs ne peuvent pas récupérer des informations de pile précieuses à partir d'erreurs détectées par onerror.

Polyfilling window.onerror avec try / catch

Mais il existe une solution de contournement – vous pouvez placer du code dans votre application dans un try / catch and catch l'erreur vous-même. Cet objet d'erreur contiendra notre propriété convoitée stack dans chaque navigateur moderne.

Considérons la méthode d'assistance suivante, invoke qui appelle une fonction sur un objet avec un tableau d'arguments:

 fonction invoke (obj, méthode, args) {
    return obj [method] .apply (ceci, args);
}

invoquer (Math, 'max', [1, 2]); // renvoie 2

Voici encore invoquer cette fois enveloppé dans try / catch, afin de capturer toute erreur lancée:

 invoke (obj, method, args) {
  essayez {
    return obj [method] .apply (ceci, args);
  } catch (e) {
    captureError (e); // signaler l'erreur
    lancer e; // relancer l'erreur
  }
}

invoquer (Math, 'Highest', [1, 2]); // renvoie une erreur, aucune méthode Math.highest

Bien sûr, le faire manuellement partout est assez lourd. Vous pouvez le rendre plus facile en créant une fonction d'utilitaire wrapper générique:

 function wrapErrors (fn) {
  // n'emballe pas la fonction plus d'une fois
  if (! fn .__ wrapped__) {
    fn .__ wrapped__ = function () {
      essayez {
        return fn.apply (this, arguments);
      } catch (e) {
        captureError (e); // signaler l'erreur
        lancer e; // relancer l'erreur
      }
    }
  }

  return fn .__ wrapped__;
}

var invoke = wrapErrors (fonction (obj, méthode, args) {
  return obj [method] .apply (ceci, args);
});

invoquer (Math, 'Highest', [1, 2]); // aucune méthode Math.highest

Étant donné que JavaScript est à un seul thread, vous n'avez pas besoin d'utiliser l'enveloppe partout, juste au début de chaque nouvelle pile.

Cela signifie que vous devrez envelopper les déclarations de fonctions:

  • Au début de votre application (par exemple, dans $ (document) .ready si vous utilisez jQuery)
  • Dans les gestionnaires d'événements (par exemple, addEventListener ou $. fn.click )
  • Rappels par minuterie (par exemple, setTimeout ou requestAnimationFrame )

Par exemple:

 $ (wrapErrors (function () {// début de l'application
  doSynchronousStuff1 (); // n'a pas besoin d'être enveloppé

  setTimeout (wrapErrors (function () {
    doSynchronousStuff2 (); // n'a pas besoin d'être enveloppé
  });

  $ ('.foo') .cliquez sur (wrapErrors (function () {
    doSynchronousStuff3 (); // n'a pas besoin d'être enveloppé
  });
}));

Si ça a l'air d'être un sacré boulot, ne vous inquiétez pas! La plupart des bibliothèques de rapports d'erreurs ont des mécanismes pour augmenter les fonctions intégrées comme addEventListener et setTimeout pour que vous n'ayez pas besoin d'appeler un utilitaire de wrapping à chaque fois. Et, oui, le raven-js fait cela aussi.

Transmettre l'erreur à vos serveurs

Ok, donc vous avez fait votre travail – vous avez branché sur window.onerror et vous En outre, les fonctions d'enveloppement sont essayées dans catch / catch afin d'obtenir le plus d'informations possible sur les erreurs.

Il n'y a qu'une dernière étape: transmettre l'information d'erreur à vos serveurs. Pour que cela fonctionne, vous devez configurer un service Web de rapports qui accepte vos données d'erreur via HTTP, les enregistre dans un fichier et / ou les stocke dans une base de données.

Si ce service Web est sur le même domaine que votre application web, utilisez simplement XMLHttpRequest. Dans l'exemple ci-dessous, nous utilisons la fonction AJAX de jQuery pour transmettre les données à nos serveurs:

 function captureError (ex) {
  var errorData = {
    nom: ex.nom, // par ex. ReferenceError
    message: ex.line, // par ex. x est indéfini
    url: document.location.href,
    pile: ex.stack // pile de chaînes; rappelez-vous, différent par navigateur!
  }

  $ .post ('/ logger / js /', {
    données: errorData
  });
}

Notez que, si vous devez transmettre votre erreur entre différentes origines, votre point de terminaison de reporting devra prendre en charge le partage de ressources d'origine (CORS).

Résumé

Si vous avez atteint ce stade, avoir tous les outils dont vous avez besoin pour rouler votre propre bibliothèque de rapports d'erreurs de base et l'intégrer avec votre application:

  • Comment fonctionne window.onerror et quels navigateurs sont supportés
  • Comment utiliser try / catch pour capturer des traces de pile où window.onerror fait défaut
  • Transmettre des données d'erreur à vos serveurs

Bien sûr, si vous ne voulez pas vous embêter avec tout cela, il y a beaucoup de commerciaux et des outils open-source qui font tout le gros du reporting côté client pour vous. (Psst: vous pourriez vouloir essayer Sentry à déboguer JavaScript .)

C'est tout! Heureux erreur de surveillance .




Source link