Mécanisme d'événement global JavaScript

350

Je voudrais attraper chaque erreur de fonction non définie levée. Existe-t-il une fonction globale de gestion des erreurs en JavaScript? Le cas d'utilisation capture des appels de fonction depuis le flash qui ne sont pas définis.

Brian Tompsett - 汤 莱恩
la source
Que voulez-vous faire avec une erreur une fois que vous l'avez détectée? Avez-vous juste besoin de le journaliser pour pouvoir créer la fonction manquante, ou cherchez-vous à empêcher les exceptions de casser votre code?
Dan Herbert
2
Je voudrais obtenir le nom de la fonction manquante appelée et en fonction de la présence d'une chaîne, appeler ma propre fonction. Tout appel à une fonction avec la chaîne 'close' appelle par exemple my close (). Je voudrais également piéger l'erreur à ce stade.
2
exceptionsjs.com fournit cette fonctionnalité et peut être paramétré pour détecter uniquement les erreurs liées à une fonctionnalité non définie avec sa fonctionnalité de «garde».
Steven Wexler

Réponses:

193

Cela vous aide-t-il:

<script type="text/javascript">
window.onerror = function() {
    alert("Error caught");
};

xxx();
</script>

Je ne sais pas comment il gère les erreurs Flash ...

Mise à jour: cela ne fonctionne pas dans Opera, mais je pirate Dragonfly en ce moment pour voir ce qu'il obtient. La suggestion de pirater Dragonfly est venue de cette question:

Fenêtre d'imitation. onerror dans Opera en utilisant javascript

Ionuț G. Stan
la source
3
Avec l'ajout des paramètres msg, file_loc, line_no, cela devrait le faire pour moi. Merci!
1
Dois-je comprendre correctement que ce code remplace tous les gestionnaires d'erreurs existants?
Mars Robertson
@MarsRobertson No.
atilkan
@atilkan window.onerror = function() { alert(42) };maintenant le code dans la réponse: window.onerror = function() { alert("Error caught"); };pas remplacé, je ne sais toujours pas ..
Mars Robertson
@MarsRobertson je comprends. Il écrase probablement. Oui, c'est juste testé. Utiliser addEventListener serait mieux.
atilkan
646

Comment attraper les erreurs Javascript non gérées

Attribuez l' window.onerrorévénement à un gestionnaire d'événements comme:

<script type="text/javascript">
window.onerror = function(msg, url, line, col, error) {
   // Note that col & error are new to the HTML 5 spec and may not be 
   // supported in every browser.  It worked for me in Chrome.
   var extra = !col ? '' : '\ncolumn: ' + col;
   extra += !error ? '' : '\nerror: ' + error;

   // You can view the information in an alert to see things working like this:
   alert("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);

   // TODO: Report this error via ajax so you can keep track
   //       of what pages have JS issues

   var suppressErrorAlert = true;
   // If you return true, then error alerts (like in older versions of 
   // Internet Explorer) will be suppressed.
   return suppressErrorAlert;
};
</script>

Comme indiqué dans le code, si la valeur de retour de window.onerroresttrue le navigateur doit supprimer l'affichage d'une boîte de dialogue d'alerte.

Quand l'événement window.onerror se déclenche-t-il?

En bref, l'événement est déclenché lorsque 1.) il y a une exception non interceptée ou 2.) une erreur de compilation se produit.

exceptions non capturées

  • jeter "quelques messages"
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document ;, une exception de sécurité

erreur de compilation

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, il tentera de compiler le premier argument sous forme de script

Navigateurs prenant en charge window.onerror

  • Chrome 13+
  • Firefox 6.0+
  • Internet Explorer 5.5+
  • Opera 11.60+
  • Safari 5.1+

Capture d'écran:

Exemple du code onerror ci-dessus en action après l'avoir ajouté à une page de test:

<script type="text/javascript">
call_something_undefined();
</script>

Alerte Javascript affichant les informations d'erreur détaillées par l'événement window.onerror

Exemple de rapport d'erreur AJAX

var error_data = {
    url: document.location.href,
};

if(error != null) {
    error_data['name'] = error.name; // e.g. ReferenceError
    error_data['message'] = error.line;
    error_data['stack'] = error.stack;
} else {
    error_data['msg'] = msg;
    error_data['filename'] = filename;
    error_data['line'] = line;
    error_data['col'] = col;
}

var xhr = new XMLHttpRequest();

xhr.open('POST', '/ajax/log_javascript_error');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
    if (xhr.status === 200) {
        console.log('JS error logged');
    } else if (xhr.status !== 200) {
        console.error('Failed to log JS error.');
        console.error(xhr);
        console.error(xhr.status);
        console.error(xhr.responseText);
    }
};
xhr.send(JSON.stringify(error_data));

JSFiddle:

https://jsfiddle.net/nzfvm44d/

Références:

Sam
la source
3
vaut la peine de mentionner que firefox ne rend pas le message d'erreur lorsqu'il throwest fait manuellement. stackoverflow.com/questions/15036165/…
Sebas
2
Très bonne réponse. Vous pouvez implémenter le "Signaler cette erreur via ajax" avec un package comme JSNLog, qui effectue la journalisation ajax et côté serveur pour vous.
user1147862
4
En plus de cette réponse, j'ai ajouté l'objet err afin que je puisse obtenir la trace de la pile. stackoverflow.com/a/20972210/511438 . Maintenant, je peux développer avec plus de commentaires car mes erreurs de développement apparaissent sous la forme d'une boîte en haut de la page (telle que je l'ai créée).
Valamas
Ne serait-il pas préférable de regrouper votre implémentation onerror dans un bloc try-catch (ignore), empêchant votre onerror () de lancer encore plus d'erreurs? (pas que ce soit le cas ici, mais juste pour être sûr)
Pieter De Bie
1
@ super1ha1 Pouvez-vous fournir un jsfiddle? Je ne pense pas que les navigateurs introduiraient un changement de rupture où le gestionnaire d'événements onerror arrête de déclencher des événements. Vous pouvez essayer ce jsfiddle pour voir le gestionnaire onerror fonctionner: jsfiddle.net/nzfvm44d Cela fonctionne toujours pour moi dans la version 62.0.3202.94 de Chrome (version officielle) (64 bits).
Sam
38

gestion sophistiquée des erreurs

Si votre gestion des erreurs est très sophistiquée et peut donc déclencher une erreur elle-même, il est utile d'ajouter un indicateur indiquant si vous êtes déjà en "mode de gestion des erreurs". Ainsi:

var appIsHandlingError = false;

window.onerror = function() {
    if (!appIsHandlingError) {
        appIsHandlingError = true;
        handleError();
    }
};

function handleError() {
    // graceful error handling
    // if successful: appIsHandlingError = false;
}

Sinon, vous pourriez vous retrouver dans une boucle infinie.

SunnyRed
la source
29
Ou un moyen plus sûr serait d'utiliser try-catch autour de la handleErrorméthode.
Aidiakapi
8
Vous ne pouvez pas utiliser try catch si vous avez un appel ansynchrone dans votre gestion des erreurs. Il est donc la solution reste e bonne solution
Emrys Myrooin
@EmrysMyrooin Cela provoquera toujours une synchronisation ou une boucle asynchrone par la gestion des erreurs et se bloquera probablement sans informations de pile utilisables.
inf3rno
1
Je pense que le drapeau devrait être réinitialisé à un moment donné, non? Je crois que nous avons besoin d'un essai / rattrapage si gros si, et assurez-vous que le drapeau est réinitialisé juste avant de quitter la méthode onerror. Sinon, une seule erreur sera gérée.
Seb
23

Essayez Atatus qui fournit un suivi avancé des erreurs et une surveillance réelle des utilisateurs pour les applications Web modernes.

https://www.atatus.com/

Permettez-moi d'expliquer comment obtenir des traces de pile qui sont raisonnablement complètes dans tous les navigateurs.

Gestion des erreurs en JavaScript

Chrome et Opera modernes prennent entièrement en charge le projet de spécification HTML 5 pour ErrorEvent et window.onerror. Dans ces deux navigateurs, vous pouvez soit utiliser window.onerror, soit vous lier correctement à l'événement «erreur»:

// Only Chrome & Opera pass the error object.
window.onerror = function (message, file, line, col, error) {
    console.log(message, "from", error.stack);
    // You can send data to your server
    // sendError(data);
};
// Only Chrome & Opera have an error attribute on the event.
window.addEventListener("error", function (e) {
    console.log(e.error.message, "from", e.error.stack);
    // You can send data to your server
    // sendError(data);
})

Malheureusement, Firefox, Safari et IE sont toujours là et nous devons également les prendre en charge. Comme le stacktrace n'est pas disponible danswindow.onerror nous devons faire un peu plus de travail.

Il s'avère que la seule chose que nous pouvons faire pour obtenir des traces de pile à partir d'erreurs est d'envelopper tout notre code dans un try{ }catch(e){ }bloc, puis de regarder e.stack. Nous pouvons rendre le processus un peu plus facile avec une fonction appelée wrap qui prend une fonction et renvoie une nouvelle fonction avec une bonne gestion des erreurs.

function wrap(func) {
    // Ensure we only wrap the function once.
    if (!func._wrapped) {
        func._wrapped = function () {
            try{
                func.apply(this, arguments);
            } catch(e) {
                console.log(e.message, "from", e.stack);
                // You can send data to your server
                // sendError(data);
                throw e;
            }
        }
    }
    return func._wrapped;
};

Cela marche. Toute fonction que vous encapsulez manuellement aura une bonne gestion des erreurs, mais il se trouve que nous pouvons le faire automatiquement pour vous dans la plupart des cas.

En modifiant la définition globale de addEventListenerafin qu'il encapsule automatiquement le rappel, nous pouvons insérer automatiquement try{ }catch(e){ }autour de la plupart du code. Cela permet au code existant de continuer à fonctionner, mais ajoute un suivi des exceptions de haute qualité.

var addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEventListener.call(this, event, wrap(callback), bubble);
}

Nous devons également nous assurer que cela removeEventListenercontinue de fonctionner. Pour le moment, ce ne sera pas le cas car l'argument de addEventListenerest modifié. Encore une fois, nous devons seulement corriger cela sur l' prototypeobjet:

var removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventListener.call(this, event, callback._wrapped || callback, bubble);
}

Transmettez les données d'erreur à votre backend

Vous pouvez envoyer des données d'erreur à l'aide de la balise d'image comme suit

function sendError(data) {
    var img = newImage(),
        src = 'http://yourserver.com/jserror&data=' + encodeURIComponent(JSON.stringify(data));

    img.crossOrigin = 'anonymous';
    img.onload = function success() {
        console.log('success', data);
    };
    img.onerror = img.onabort = function failure() {
        console.error('failure', data);
    };
    img.src = src;
}

Avis de non-responsabilité: je suis développeur Web sur https://www.atatus.com/ .

Fizer Khan
la source
Qu'est-ce que yourserver.com/jserror ? REST, service Web, service Wcf ? Tout simple sur le backend?
Kiquenet
Si vous souhaitez envoyer des erreurs js du navigateur de l'utilisateur à votre serveur. vous devez écrire votre backend ( http://yourserver.com) pour recevoir et stocker. Si vous choisissez atatus.com , vous n'avez rien à faire. Incluez simplement deux lignes de script dans votre page.
Fizer Khan
18

Il semble que window.onerrorcela ne donne pas accès à toutes les erreurs possibles. Plus précisément, il ignore:

  1. <img> erreurs de chargement (réponse> = 400).
  2. <script> erreurs de chargement (réponse> = 400).
  3. erreurs globales si vous avez de nombreuses autres bibliothèques dans votre application manipulant également window.onerror de manière inconnue (jquery, angular, etc.).
  4. probablement de nombreux cas que je n'ai pas rencontrés après avoir exploré cela maintenant (iframes, débordement de pile, etc.).

Voici le début d'un script qui détecte bon nombre de ces erreurs, afin que vous puissiez ajouter un débogage plus robuste à votre application pendant le développement.

(function(){

/**
 * Capture error data for debugging in web console.
 */

var captures = [];

/**
 * Wait until `window.onload`, so any external scripts
 * you might load have a chance to set their own error handlers,
 * which we don't want to override.
 */

window.addEventListener('load', onload);

/**
 * Custom global function to standardize 
 * window.onerror so it works like you'd think.
 *
 * @see http://www.quirksmode.org/dom/events/error.html
 */

window.onanyerror = window.onanyerror || onanyerrorx;

/**
 * Hook up all error handlers after window loads.
 */

function onload() {
  handleGlobal();
  handleXMLHttp();
  handleImage();
  handleScript();
  handleEvents();
}

/**
 * Handle global window events.
 */

function handleGlobal() {
  var onerrorx = window.onerror;
  window.addEventListener('error', onerror);

  function onerror(msg, url, line, col, error) {
    window.onanyerror.apply(this, arguments);
    if (onerrorx) return onerrorx.apply(null, arguments);
  }
}

/**
 * Handle ajax request errors.
 */

function handleXMLHttp() {
  var sendx = XMLHttpRequest.prototype.send;
  window.XMLHttpRequest.prototype.send = function(){
    handleAsync(this);
    return sendx.apply(this, arguments);
  };
}

/**
 * Handle image errors.
 */

function handleImage() {
  var ImageOriginal = window.Image;
  window.Image = ImageOverride;

  /**
   * New `Image` constructor. Might cause some problems,
   * but not sure yet. This is at least a start, and works on chrome.
   */

  function ImageOverride() {
    var img = new ImageOriginal;
    onnext(function(){ handleAsync(img); });
    return img;
  }
}

/**
 * Handle script errors.
 */

function handleScript() {
  var HTMLScriptElementOriginal = window.HTMLScriptElement;
  window.HTMLScriptElement = HTMLScriptElementOverride;

  /**
   * New `HTMLScriptElement` constructor.
   *
   * Allows us to globally override onload.
   * Not ideal to override stuff, but it helps with debugging.
   */

  function HTMLScriptElementOverride() {
    var script = new HTMLScriptElement;
    onnext(function(){ handleAsync(script); });
    return script;
  }
}

/**
 * Handle errors in events.
 *
 * @see http://stackoverflow.com/questions/951791/javascript-global-error-handling/31750604#31750604
 */

function handleEvents() {
  var addEventListenerx = window.EventTarget.prototype.addEventListener;
  window.EventTarget.prototype.addEventListener = addEventListener;
  var removeEventListenerx = window.EventTarget.prototype.removeEventListener;
  window.EventTarget.prototype.removeEventListener = removeEventListener;

  function addEventListener(event, handler, bubble) {
    var handlerx = wrap(handler);
    return addEventListenerx.call(this, event, handlerx, bubble);
  }

  function removeEventListener(event, handler, bubble) {
    handler = handler._witherror || handler;
    removeEventListenerx.call(this, event, handler, bubble);
  }

  function wrap(fn) {
    fn._witherror = witherror;

    function witherror() {
      try {
        fn.apply(this, arguments);
      } catch(e) {
        window.onanyerror.apply(this, e);
        throw e;
      }
    }
    return fn;
  }
}

/**
 * Handle image/ajax request errors generically.
 */

function handleAsync(obj) {
  var onerrorx = obj.onerror;
  obj.onerror = onerror;
  var onabortx = obj.onabort;
  obj.onabort = onabort;
  var onloadx = obj.onload;
  obj.onload = onload;

  /**
   * Handle `onerror`.
   */

  function onerror(error) {
    window.onanyerror.call(this, error);
    if (onerrorx) return onerrorx.apply(this, arguments);
  };

  /**
   * Handle `onabort`.
   */

  function onabort(error) {
    window.onanyerror.call(this, error);
    if (onabortx) return onabortx.apply(this, arguments);
  };

  /**
   * Handle `onload`.
   *
   * For images, you can get a 403 response error,
   * but this isn't triggered as a global on error.
   * This sort of standardizes it.
   *
   * "there is no way to get the HTTP status from a 
   * request made by an img tag in JavaScript."
   * @see http://stackoverflow.com/questions/8108636/how-to-get-http-status-code-of-img-tags/8108646#8108646
   */

  function onload(request) {
    if (request.status && request.status >= 400) {
      window.onanyerror.call(this, request);
    }
    if (onloadx) return onloadx.apply(this, arguments);
  }
}

/**
 * Generic error handler.
 *
 * This shows the basic implementation, 
 * which you could override in your app.
 */

function onanyerrorx(entity) {
  var display = entity;

  // ajax request
  if (entity instanceof XMLHttpRequest) {
    // 400: http://example.com/image.png
    display = entity.status + ' ' + entity.responseURL;
  } else if (entity instanceof Event) {
    // global window events, or image events
    var target = entity.currentTarget;
    display = target;
  } else {
    // not sure if there are others
  }

  capture(entity);
  console.log('[onanyerror]', display, entity);
}

/**
 * Capture stuff for debugging purposes.
 *
 * Keep them in memory so you can reference them
 * in the chrome debugger as `onanyerror0` up to `onanyerror99`.
 */

function capture(entity) {
  captures.push(entity);
  if (captures.length > 100) captures.unshift();

  // keep the last ones around
  var i = captures.length;
  while (--i) {
    var x = captures[i];
    window['onanyerror' + i] = x;
  }
}

/**
 * Wait til next code execution cycle as fast as possible.
 */

function onnext(fn) {
  setTimeout(fn, 0);
}

})();

Il pourrait être utilisé comme ceci:

window.onanyerror = function(entity){
  console.log('some error', entity);
};

Le script complet a une implémentation par défaut qui essaie d'imprimer une version "d'affichage" semi-lisible de l'entité / erreur qu'il reçoit. Peut être utilisé comme source d'inspiration pour un gestionnaire d'erreurs spécifique à l'application. L'implémentation par défaut conserve également une référence aux 100 dernières entités d'erreur, vous pouvez donc les inspecter dans la console Web après qu'elles se produisent, comme:

window.onanyerror0
window.onanyerror1
...
window.onanyerror99

Remarque: cela fonctionne en remplaçant les méthodes sur plusieurs navigateurs / constructeurs natifs. Cela peut avoir des effets secondaires imprévus. Cependant, il a été utile d'utiliser pendant le développement, pour déterminer où se produisent les erreurs, pour envoyer des journaux à des services comme NewRelic ou Sentry pendant le développement afin que nous puissions mesurer les erreurs pendant le développement, et lors de la mise en scène afin que nous puissions déboguer ce qui se passe à un niveau plus profond. Il peut ensuite être désactivé en production.

J'espère que cela t'aides.

Lance Pollard
la source
1
Apparemment, les images déclenchent l'événement d'erreur: stackoverflow.com/a/18152753/607033
inf3rno
6
// display error messages for a page, but never more than 3 errors
window.onerror = function(msg, url, line) {
if (onerror.num++ < onerror.max) {
alert("ERROR: " + msg + "\n" + url + ":" + line);
return true;
}
}
onerror.max = 3;
onerror.num = 0;
GibboK
la source
6

Il faut également conserver le rappel onerror précédemment associé

<script type="text/javascript">

(function() {
    var errorCallback = window.onerror;
    window.onerror = function () {
        // handle error condition
        errorCallback && errorCallback.apply(this, arguments);
    };
})();

</script>
Apoorv Saxena
la source
3

Si vous voulez un moyen unifié de gérer à la fois les erreurs non capturées et les rejets de promesses non gérés, vous pouvez jeter un œil à la bibliothèque non capturée .

ÉDITER

<script type="text/javascript" src=".../uncaught/lib/index.js"></script>

<script type="text/javascript">
    uncaught.start();
    uncaught.addListener(function (error) {
        console.log('Uncaught error or rejection: ', error.message);
    });
</script>

Il écoute la fenêtre. rejet non géré en plus de window.onerror.

Aleksandr Oleynikov
la source
2
Lance Pollard dans la réponse ci - dessus a mentionné quelques erreurs, qu'une erreur normale ne gère pas. Votre bibliothèque les gère-t-elle? Si certains d'entre eux, veuillez préciser lesquels?
Michael Freidgeim
@ MiFreidgeimSO-stopbeingevil Je viens de vérifier le code source - non, malheureusement, ce n'est pas le cas. Aleksandr Oleynikov: Ce serait génial si vous pouviez soutenir cela - je vais ensuite transformer mon downvote en upvote ;-)
brillout
2

Je recommanderais d' essayer Trackjs .

C'est la journalisation des erreurs en tant que service.

C'est incroyablement simple à installer. Ajoutez simplement une ligne <script> à chaque page et c'est tout. Cela signifie également qu'il sera incroyablement simple à supprimer si vous décidez que vous ne l'aimez pas.

Il existe d'autres services comme Sentry (qui est open-source si vous pouvez héberger votre propre serveur), mais il ne fait pas ce que fait Trackjs. Trackjs enregistre l'interaction de l'utilisateur entre son navigateur et votre serveur Web afin que vous puissiez réellement suivre les étapes de l'utilisateur qui ont conduit à l'erreur, par opposition à simplement une référence de numéro de fichier et de ligne (et peut-être une trace de pile).

kane
la source
2
TrackJS ne semble plus avoir de niveau gratuit , bien qu'il y ait un essai gratuit.
pkaeding
C'est très bien pour suivre et être alerté des erreurs mais cela ne résout pas vraiment la partie manipulation. Idéalement, je pense que le demandeur cherche un moyen de gérer ces erreurs afin que le reste du code fonctionne toujours.
wgp
Que faire si vous interceptez l'événement d'erreur et ajoutez un appel xhr à l'enregistreur avec la trace de pile et l'état de l'application? Comment est trackjs mieux?
inf3rno
2
@ inf3rno c'est plus qu'une trace de pile, qui ne fait que suivre le début de l'erreur. TrackJS suivra toute la session utilisateur afin que vous puissiez voir ce qui y a mené. voici une capture d'écran comme exemple trackjs.com/assets/images/screenshot.png
kane
1

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

 window.onerror = function (msg, url, lineNo, columnNo, error) {
        var string = msg.toLowerCase();
        var substring = "script error";
        if (string.indexOf(substring) > -1){
            alert('Script Error: See Browser Console for Detail');
        } else {
            alert(msg, url, lineNo, columnNo, error);
        }   
      return false; 
  };
Ali Azhar
la source