Surveillance des erreurs JavaScript avec window.onerror

Le matériel, dont nous publions la traduction aujourd'hui, est dédié au traitement des erreurs JS à l'aide de window.onerror . Il s'agit d'un événement spécial du navigateur qui se déclenche lorsque des erreurs non détectées se produisent. Ici, nous expliquerons comment détecter les erreurs à l'aide du onerror événements onerror et comment envoyer des informations à leur sujet au serveur du développeur du site Web. Ce gestionnaire peut être utilisé comme base de votre propre système de collecte et d'analyse des informations d'erreur. De plus, il s'agit de l'un des mécanismes les plus importants utilisés dans les bibliothèques orientées erreur, telles que raven-js .

image

Écoute de l'événement window.onerror


Vous pouvez écouter l'événement onerror en affectant window.onerror fonction qui agit comme un gestionnaire d'erreur:

 window.onerror = function(msg, url, lineNo, columnNo, error) { // ...   ... return false; } 

Cette fonction est appelée lorsqu'une erreur se produit; les arguments suivants lui sont transmis:

  • msg - message d'erreur. Par exemple, Uncaught ReferenceError: foo is not defined .
  • url - l'adresse du script ou du document dans lequel l'erreur s'est produite. Par exemple, /dist/app.js .
  • lineNo - numéro de ligne où l'erreur s'est produite (si prise en charge).
  • columnNo - le numéro de colonne de la ligne (si pris en charge).
  • error - l'objet d' error (s'il est pris en charge).

Les quatre premiers arguments indiquent au développeur quel script, dans quelle ligne et dans quelle colonne de cette ligne une erreur s'est produite. Le dernier argument, un objet de type Error , est peut-être le plus important de tous les arguments. Parlons-en.

Objet d'erreur et propriété Error.prototype.stack


À première vue, l'objet Error n'a rien de spécial. Il contient trois propriétés assez standard - message , fileName et lineNumber . Ces données, compte tenu des informations transmises au gestionnaire d'événements window.onerror , peuvent être considérées comme redondantes.

La valeur réelle dans ce cas est la propriété non standard Error.prototype.stack . Cette propriété donne accès à la pile des appels (pile des erreurs), vous permet de savoir ce qui se passait dans le programme au moment de l'erreur, quel appel de fonction a précédé son apparition. Le suivi de la pile d'appels peut être un élément essentiel du processus de débogage. Et, malgré le fait que la propriété de stack n'est pas standard, elle est disponible dans tous les navigateurs modernes.

Voici à quoi ressemble la propriété de stack de l'objet d'erreur dans Chrome 46.

 "Error: foobar\n    at new bar (<anonymous>:241:11)\n    at foo (<anonymous>:245:5)\n at <anonymous>:250:5\n    at <anonymous>:251:3\n at <anonymous>:267:4\n at callFunction (<anonymous>:229:33)\n    at <anonymous>:239:23\n at <anonymous>:240:3\n at Object.InjectedScript.\_evaluateOn (<anonymous>:875:140)\n    at Object.InjectedScript.\_evaluateAndWrap (<anonymous>:808:34)" 

Devant nous se trouve une chaîne non formatée. Lorsque le contenu de cette propriété est présenté sous cette forme, il n'est pas pratique de travailler avec elle. Voici à quoi cela ressemblera après le formatage.

 Error: foobar at new bar (<anonymous>:241:11) at foo (<anonymous>:245:5) at callFunction (<anonymous>:229:33) at Object.InjectedScript._evaluateOn (<anonymous>:875:140) at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34) 

Maintenant, après le formatage, la pile des erreurs semble beaucoup plus claire, il devient immédiatement clair pourquoi la propriété de la stack est très importante lors du débogage des erreurs.

Cependant, ici, tout ne se passe pas bien. La propriété de stack n'est pas normalisée; elle est implémentée différemment dans différents navigateurs. Voici, par exemple, à quoi ressemble la pile d'erreurs dans Internet Explorer 11.

 Error: foobar at bar (Unknown script code:2:5) at foo (Unknown script code:6:5) at Anonymous function (Unknown script code:11:5) at Anonymous function (Unknown script code:10:2) at Anonymous function (Unknown script code:1:73) 

Vous pouvez voir, par rapport à l'exemple précédent, qu'ici non seulement un format différent pour représenter les trames de pile est utilisé, mais aussi qu'il y a moins de données pour chaque trame. Par exemple, Chrome identifie les instances d'utilisation du new mot clé et fournit des informations plus détaillées sur d'autres événements (en particulier, sur les appels de fonction. _evaluateOn et. _evaluateAndWrap ). Dans le même temps, nous n'avons comparé ici que ce que IE et Chrome donnent. D'autres navigateurs utilisent leurs propres approches pour afficher des données sur la pile et pour sélectionner les informations incluses dans ces données.

Afin de rendre tout cela uniforme, vous pouvez utiliser des outils tiers. Par exemple, raven-js utilise TraceKit pour cela. Stacktrace.js et certains autres projets ont le même objectif.

Caractéristiques de la prise en charge de window.onerror par divers navigateurs


L'événement windows.onerror existe dans les navigateurs depuis un certain temps. En particulier, il peut être trouvé dans IE6 et Firefox 2. Le problème ici est que tous les navigateurs implémentent windows.onerror de différentes manières. Par exemple, cela concerne le nombre et la structure des arguments passés aux gestionnaires de cet événement.

Voici un tableau onerror arguments passés au gestionnaire onerror dans les principaux navigateurs.
Navigateur
message
url
ligneNon
colNo
errorObj
Firefox
Il y a
Il y a
Il y a
Il y a
Il y a
Chrome
Il y a
Il y a
Il y a
Il y a
Il y a
Bord
Il y a
Il y a
Il y a
Il y a
Il y a
IE 11
Il y a
Il y a
Il y a
Il y a
Il y a
IE10
Il y a
Il y a
Il y a
Il y a
Non
IE 9.8
Il y a
Il y a
Il y a
Non
Non
Safari 10 et supérieur
Il y a
Il y a
Il y a
Il y a
Il y a
Safari 9
Il y a
Il y a
Il y a
Il y a
Non
Navigateur Android 4.4
Il y a
Il y a
Il y a
Il y a
Non

Sans surprise, Internet Explorer 8, 9 et 10 ont un support limité pour onerror . Cependant, il peut sembler inhabituel que dans le navigateur Safari, la prise en charge de l'objet d'erreur n'apparaisse que dans la 10e version, publiée en 2016. En outre, certains appareils mobiles hérités utilisent le navigateur Android standard, qui ne prend pas en charge l'objet d'erreur. Dans les versions modernes d'Android, ce navigateur a été remplacé par Chrome Mobile.

S'il n'y a pas d'objet d'erreur à notre disposition, alors il n'y a pas de données sur la trace de la pile. Cela signifie que les navigateurs qui ne prennent pas en charge l'objet de l'erreur ne fournissent pas d'informations de pile dans le script standard pour utiliser le gestionnaire onerror . Et cela, comme nous l'avons dit, est très important.

Développement Polyfill pour window.onerror utilisant la construction try / catch


Afin d'obtenir des informations sur la pile dans les navigateurs qui ne prennent pas en charge la transmission d'un objet d' onerror au gestionnaire onerror , vous pouvez utiliser l'astuce suivante. Vous pouvez encapsuler le code dans une construction try/catch et intercepter les erreurs vous-même. L'objet d'erreur résultant contiendra, dans tous les navigateurs modernes, ce dont nous avons besoin, c'est la propriété stack .
Jetez un œil au code de la méthode d'assistance invoke() , qui appelle la méthode donnée de l'objet, en lui passant un tableau d'arguments.

 function invoke(obj, method, args) { return obj[method].apply(this,args); } 

Voici comment l'utiliser.

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

Voici la même invoke() , mais maintenant l'appel de méthode est encapsulé dans try/catch , ce qui vous permet d'attraper les erreurs possibles.

 function invoke(obj, method, args) { try {   return obj[method].apply(this,args); } catch(e) {   captureError(e);//      throw e;//      } } invoke(Math,'highest',[1,2]); //  ,     Math.highest 

Bien sûr, il est très coûteux d'ajouter manuellement de telles structures à tous les endroits où elles peuvent être nécessaires. Cette tâche peut être simplifiée en créant une fonction d'assistance universelle.

 function wrapErrors(fn) { //      if(!fn.__wrapped__) {   fn.__wrapped__ = function() {     try{       return fn.apply(this,arguments);     }catch(e){       captureError(e);//          throw e;//          }   }; } return fn.__wrapped__; } var invoke = wrapErrors(function(obj, method, args) { returnobj[method].apply(this,args); }); invoke(Math,'highest',[1,2]);//,   Math.highest 

Étant donné que JavaScript utilise un modèle d'exécution de code à thread unique, ce wrapper ne doit être utilisé qu'avec les appels de fonction qui se trouvent au début de nouvelles piles. Il n'est pas nécessaire d'envelopper tous les appels de fonction.

En conséquence, il s'avère que cette fonction doit être utilisée aux endroits suivants:

  • Où l'application démarre (par exemple, lors de l'utilisation de jQuery, dans la fonction $(document).ready )
  • Dans les gestionnaires d'événements (par exemple, dans addEventListener ou dans les constructions de la forme $.fn.click )
  • Dans les rappels appelés par des événements de temporisation (par exemple, il s'agit de setTimeout ou requestAnimationFrame )

Voici un exemple d'utilisation de la fonction wrapErrors .

 $(wrapErrors(function () {//    doSynchronousStuff1();//     setTimeout(wrapErrors(function () {   doSynchronousStuff2();//      })); $('.foo').click(wrapErrors(function () {   doSynchronousStuff3();//     })); })); 

De telles constructions peuvent être ajoutées au code vous-même, mais cette tâche prend trop de temps. Comme alternative pratique dans de telles situations, vous pouvez envisager des bibliothèques pour travailler avec les erreurs, qui, par exemple, ont des mécanismes qui addEventListener et setTimeout outils pour détecter les erreurs.

Erreur de transfert vers le serveur


Donc, nous avons maintenant à notre disposition des moyens pour intercepter les informations sur les erreurs en utilisant windows.onerror ou en utilisant des fonctions auxiliaires basées sur try/catch . Ces erreurs se produisent du côté client et, après leur interception, nous aimerions les traiter et prendre des mesures pour les éliminer. Pour ce faire, ils doivent être transférés sur notre serveur. Pour ce faire, vous devez préparer un service Web qui recevrait des informations sur les erreurs via HTTP, puis les enregistrer d'une manière ou d'une autre pour un traitement ultérieur, par exemple - écrire dans un fichier journal ou une base de données.

Si ce service Web se trouve sur le même domaine que l'application Web, XMLHttpRequest suffira. L'exemple suivant montre comment utiliser une fonction pour exécuter des requêtes AJAX à partir de jQuery pour transférer des données vers un serveur.

 function captureError(ex){ var errorData = {   name:ex.name,// : ReferenceError   message:ex.line,// : x is undefined   url:document.location.href,   stack:ex.stack//   ; ,     ! }; $.post('/logger/js/',{   data:errorData }); } 

Gardez à l'esprit que si vous devez envoyer des demandes interdomaines pour envoyer des informations sur les erreurs au serveur, vous devrez prendre en charge ces demandes.

Résumé


Vous avez appris les bases de la création d'un service pour détecter les erreurs et envoyer des informations à leur sujet au serveur. En particulier, nous avons examiné ici les questions suivantes:

  • Caractéristiques de l'événement onerror et son support dans divers navigateurs.
  • Utilisation du mécanisme try/catch pour obtenir des informations sur la pile d'appels dans les cas où onerror ne prend pas en charge l'utilisation de l'objet d'erreur.
  • Transférez les données d'erreur sur le serveur du développeur.

Après avoir appris comment fonctionnent les mécanismes ci-dessus, vous avez acquis des connaissances de base qui vous permettront de commencer à créer votre propre système pour traiter les erreurs, en clarifiant des détails supplémentaires pendant le travail. Ce scénario est peut-être particulièrement pertinent dans les cas où il s'agit d'une certaine application dans laquelle, par exemple, pour des raisons de sécurité, il n'est pas prévu d'utiliser des bibliothèques tierces. Si votre application autorise l'utilisation de code tiers, vous pouvez très bien trouver le bon outil pour surveiller les erreurs JS. Parmi ces outils figurent Sentry , Rollbar , TrackJS et d'autres projets similaires.

Chers lecteurs! Quels outils de surveillance des erreurs JS utilisez-vous?

Source: https://habr.com/ru/post/fr413173/


All Articles