Globaler JavaScript-Ereignismechanismus

350

Ich möchte jeden undefinierten Funktionsfehler abfangen. Gibt es eine globale Fehlerbehandlungsfunktion in JavaScript? Der Anwendungsfall ist das Abfangen von Funktionsaufrufen von Flash, die nicht definiert sind.

Brian Tompsett - 汤 莱恩
quelle
Was möchten Sie mit einem Fehler tun, wenn Sie ihn abfangen? Müssen Sie es nur protokollieren, damit Sie die fehlende Funktion erstellen können, oder möchten Sie verhindern, dass Ausnahmen Ihren Code beschädigen?
Dan Herbert
2
Ich möchte den Namen der fehlenden Funktion aufrufen lassen und basierend auf dem Vorhandensein eines Strings meine eigene Funktion aufrufen. Jeder Aufruf einer Funktion mit der Zeichenfolge 'close' würde zum Beispiel my close () aufrufen. Ich möchte den Fehler auch an dieser Stelle einfangen.
2
Ausnahmenjs.com bietet diese Funktionalität und kann so angepasst werden, dass nur Fehler im Zusammenhang mit undefinierten Funktionen mit der Funktion "Guard" abgefangen werden.
Steven Wexler

Antworten:

192

Hilft Ihnen das:

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

xxx();
</script>

Ich bin mir nicht sicher, wie es mit Flash-Fehlern umgeht ...

Update: Es funktioniert nicht in Opera, aber ich hacke gerade Dragonfly, um zu sehen, was es bekommt. Der Vorschlag, Dragonfly zu hacken, ergab sich aus dieser Frage:

Mimic Window. Fehler in Opera mit Javascript

Ionuț G. Stan
quelle
3
Mit dem Hinzufügen der Parameter msg, file_loc, line_no sollte dies für mich erledigt werden. Vielen Dank!
1
Verstehe ich richtig, dass dieser Code vorhandene Fehlerbehandlungsroutinen überschreibt?
Mars Robertson
@ MarsRobertson Nr.
Atilkan
@atilkan window.onerror = function() { alert(42) };jetzt der Code in der Antwort: window.onerror = function() { alert("Error caught"); };nicht überschreiben, ich bin immer noch unsicher ..
Mars Robertson
@ MarsRobertson Ich verstehe. Es überschreibt wahrscheinlich. Ja, gerade getestet. Die Verwendung von addEventListener wäre besser.
Atilkan
646

So fangen Sie nicht behandelte Javascript-Fehler ab

Weisen Sie das window.onerrorEreignis einem Ereignishandler wie folgt zu:

<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>

Wie im Code kommentiert, sollte der Browser das Anzeigen eines Warndialogs unterdrücken , wenn der Rückgabewert von window.onerrorist true.

Wann wird das window.onerror-Ereignis ausgelöst?

Kurz gesagt, das Ereignis wird ausgelöst, wenn entweder 1.) eine nicht erfasste Ausnahme vorliegt oder 2.) ein Kompilierungszeitfehler auftritt.

ungefangene Ausnahmen

  • "einige Nachrichten werfen"
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document;, eine Sicherheitsausnahme

Kompilierungsfehler

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);wird versucht, das erste Argument als Skript zu kompilieren

Browser, die window.onerror unterstützen

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

Bildschirmfoto:

Beispiel für den obigen Fehlercode in Aktion, nachdem dieser zu einer Testseite hinzugefügt wurde:

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

Javascript-Warnung mit Fehlerinformationen, die im Ereignis window.onerror aufgeführt sind

Beispiel für die AJAX-Fehlerberichterstattung

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/

Verweise:

Sam
quelle
3
Erwähnenswert ist, dass Firefox die Fehlermeldung nicht zurückgibt, wenn throwsie manuell erstellt wird. stackoverflow.com/questions/15036165/…
Sebas
2
Gute Antwort. Sie können "Diesen Fehler über Ajax melden" mit einem Paket wie JSNLog implementieren, das die Ajax- und serverseitige Protokollierung für Sie durchführt.
user1147862
4
Zusätzlich zu dieser Antwort habe ich das err-Objekt hinzugefügt, damit ich den Stack-Trace erhalten kann. stackoverflow.com/a/20972210/511438 . Jetzt kann ich mit mehr Feedback entwickeln, da meine Entwicklungsfehler als Feld oben auf der Seite angezeigt werden (wie ich es erstellt habe).
Valamas
Wäre es nicht besser, Ihre Onerror-Implementierung in einen Try-Catch-Block (Ignorieren) zu packen, um zu verhindern, dass Ihr Onerror () noch mehr Fehler auslöst? (nicht, dass es hier der Fall ist, aber nur um sicher zu sein)
Pieter De Bie
1
@ super1ha1 Kannst du eine jsfiddle bereitstellen? Ich glaube nicht, dass Browser eine grundlegende Änderung einführen würden, bei der der Onerror-Ereignishandler keine Ereignisse mehr auslöst. Sie können diese jsfiddle ausprobieren, um zu sehen, wie der onerror-Handler funktioniert: jsfiddle.net/nzfvm44d Dies funktioniert immer noch für mich in Chrome Version 62.0.3202.94 (Official Build) (64-Bit).
Sam
38

ausgefeilte Fehlerbehandlung

Wenn Ihre Fehlerbehandlung sehr ausgefeilt ist und daher möglicherweise selbst einen Fehler auslöst, ist es hilfreich, ein Flag hinzuzufügen, das angibt, ob Sie sich bereits im "errorHandling-Modus" befinden. Wie so:

var appIsHandlingError = false;

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

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

Andernfalls könnten Sie sich in einer Endlosschleife befinden.

SunnyRed
quelle
29
Eine ausfallsicherere Methode wäre die Verwendung von Try-Catch für die handleErrorMethode.
Aidiakapi
8
Sie können try catch nicht verwenden, wenn Sie einen ansynchronen Aufruf in Ihrer Fehlerbehandlung haben. Also ist seine Lösung eine gute Lösung
Emrys Myrooin
@EmrysMyrooin Trotzdem wird es durch Fehlerbehandlung eine Synchronisierung oder eine asynchrone Schleife verursachen und wahrscheinlich ohne verwendbare Stapelinformationen abstürzen.
inf3rno
1
Ich denke, die Flagge sollte irgendwann zurückgesetzt werden, oder? Ich glaube, wir müssen versuchen / fangen, wenn es groß ist, und sicherstellen, dass das Flag direkt vor dem Verlassen der Onerror-Methode zurückgesetzt wird. Andernfalls wird nur ein Fehler behandelt.
Seb
23

Probieren Sie Atatus aus , das erweiterte Fehlerverfolgung und Überwachung realer Benutzer für moderne Webanwendungen bietet.

https://www.atatus.com/

Lassen Sie mich erklären, wie Sie Stacktraces erhalten, die in allen Browsern einigermaßen vollständig sind.

Fehlerbehandlung in JavaScript

Modern Chrome und Opera unterstützen die HTML 5-Entwurfsspezifikation für ErrorEvent und vollständig window.onerror. In beiden Browsern können Sie window.onerrordas Fehlerereignis entweder ordnungsgemäß verwenden oder ordnungsgemäß binden:

// 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);
})

Leider gibt es Firefox, Safari und IE immer noch und wir müssen sie auch unterstützen. Da der Stacktrace in nicht verfügbar ist, müssen window.onerrorwir etwas mehr arbeiten.

Es stellt sich heraus, dass das einzige, was wir tun können, um Stacktraces von Fehlern zu erhalten, darin besteht, unseren gesamten Code in einen try{ }catch(e){ }Block zu packen und dann zu betrachten e.stack. Wir können den Prozess mit einer Funktion namens wrap etwas vereinfachen, die eine Funktion übernimmt und eine neue Funktion mit guter Fehlerbehandlung zurückgibt.

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;
};

Das funktioniert. Jede Funktion, die Sie manuell umbrechen, hat eine gute Fehlerbehandlung, aber es stellt sich heraus, dass wir sie in den meisten Fällen automatisch für Sie erledigen können.

Indem Sie die globale Definition von addEventListenerso ändern , dass der Rückruf automatisch umbrochen wird, können wir den try{ }catch(e){ }größten Teil des Codes automatisch einfügen . Auf diese Weise kann vorhandener Code weiterarbeiten, es wird jedoch eine qualitativ hochwertige Ausnahmeverfolgung hinzugefügt.

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

Wir müssen auch sicherstellen, dass removeEventListenerdas weiter funktioniert. Im Moment wird es nicht, weil das Argument zu addEventListenergeändert wird. Wieder müssen wir dies nur am prototypeObjekt beheben :

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

Übertragen Sie Fehlerdaten an Ihr Backend

Sie können Fehlerdaten mithilfe des Image-Tags wie folgt senden

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;
}

Haftungsausschluss: Ich bin ein Webentwickler unter https://www.atatus.com/ .

Fizer Khan
quelle
Was ist yourserver.com/jserror ? REST, Web Service, Wcf Service ? Irgendwelche einfachen über Backend?
Kiquenet
Wenn Sie js-Fehler vom Benutzerbrowser an Ihren Server senden möchten. Sie müssen Ihr Backend ( http://yourserver.com) schreiben , um es zu empfangen und zu speichern. Wenn Sie sich für atatus.com entscheiden , müssen Sie nichts tun. Fügen Sie Ihrer Seite einfach zwei Skriptzeilen hinzu.
Fizer Khan
18

Es scheint, dass window.onerrordies nicht den Zugriff auf alle möglichen Fehler ermöglicht. Insbesondere ignoriert es:

  1. <img> Ladefehler (Antwort> = 400).
  2. <script> Ladefehler (Antwort> = 400).
  3. Globale Fehler, wenn Sie viele andere Bibliotheken in Ihrer App haben, die ebenfalls window.onerrorauf unbekannte Weise manipulieren (Abfrage, Winkel usw.).
  4. Wahrscheinlich sind mir viele Fälle nicht begegnet, nachdem ich dies jetzt untersucht habe (Iframes, Stapelüberlauf usw.).

Hier ist der Beginn eines Skripts, das viele dieser Fehler abfängt, damit Sie Ihrer App während der Entwicklung ein robusteres Debugging hinzufügen können.

(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);
}

})();

Es könnte so verwendet werden:

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

Das vollständige Skript verfügt über eine Standardimplementierung, die versucht, eine halb lesbare "Anzeige" -Version der empfangenen Entität / des empfangenen Fehlers auszudrucken. Kann als Inspiration für einen App-spezifischen Fehlerbehandler verwendet werden. Die Standardimplementierung enthält auch einen Verweis auf die letzten 100 Fehlerentitäten, sodass Sie sie in der Webkonsole überprüfen können, nachdem sie wie folgt aufgetreten sind:

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

Hinweis: Dies funktioniert durch Überschreiben von Methoden für mehrere Browser / native Konstruktoren. Dies kann unbeabsichtigte Nebenwirkungen haben. Es war jedoch nützlich, während der Entwicklung zu verwenden, um herauszufinden, wo Fehler auftreten, Protokolle an Dienste wie NewRelic oder Sentry während der Entwicklung zu senden, damit wir Fehler während der Entwicklung messen können, und beim Staging, um zu debuggen, was gerade passiert eine tiefere Ebene. Es kann dann in der Produktion ausgeschaltet werden.

Hoffe das hilft.

Lance Pollard
quelle
1
Anscheinend lösen Bilder das Fehlerereignis aus: 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
quelle
6

Man sollte auch den zuvor zugeordneten Fehlerrückruf beibehalten

<script type="text/javascript">

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

</script>
Apoorv Saxena
quelle
3

Wenn Sie eine einheitliche Methode suchen, um sowohl nicht erfasste Fehler als auch nicht behandelte Ablehnungen von Versprechungen zu behandeln, können Sie sich die nicht erfasste Bibliothek ansehen .

BEARBEITEN

<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>

Es hört Fenster. unbehandelte Ablehnung zusätzlich zu window.onerror.

Aleksandr Oleynikov
quelle
2
Lance Pollard erwähnte in der obigen Antwort einige Fehler, die normaler Fehler nicht behandelt. Behandelt Ihre Bibliothek sie? Wenn einige von ihnen, bitte angeben, welche?
Michael Freidgeim
@ MiFreidgeimSO-stopbeingevil Habe gerade den Quellcode überprüft - nein leider nicht. Aleksandr Oleynikov: Wäre großartig, wenn Sie dies unterstützen könnten - ich werde dann meine Ablehnung in eine positive
Bewertung
2

Ich würde empfehlen, Trackjs auszuprobieren.

Es ist eine Fehlerprotokollierung als Dienst.

Es ist erstaunlich einfach einzurichten. Fügen Sie einfach eine <script> -Zeile zu jeder Seite hinzu und fertig. Dies bedeutet auch, dass es erstaunlich einfach zu entfernen ist, wenn Sie entscheiden, dass es Ihnen nicht gefällt.

Es gibt andere Dienste wie Sentry (Open Source, wenn Sie Ihren eigenen Server hosten können), aber es macht nicht das, was Trackjs macht. Trackjs zeichnet die Interaktion des Benutzers zwischen seinem Browser und Ihrem Webserver auf, sodass Sie die Benutzerschritte, die zu dem Fehler geführt haben, tatsächlich verfolgen können, anstatt nur eine Datei- und Zeilennummernreferenz (und möglicherweise eine Stapelverfolgung).

Kane
quelle
2
TrackJS scheint keine kostenlose Stufe mehr zu haben, obwohl es eine kostenlose Testversion gibt.
pkaeding
Dies ist in Ordnung, um Fehler zu verfolgen und auf Fehler aufmerksam zu machen, löst jedoch den Behandlungsteil nicht wirklich. Idealerweise sucht der Fragesteller nach einer Möglichkeit, diese Fehler zu behandeln, damit der Rest des Codes noch ausgeführt wird.
wgp
Was ist, wenn Sie das Fehlerereignis abfangen und dem Logger einen xhr-Aufruf mit dem Stack-Trace und dem Anwendungsstatus hinzufügen? Wie ist trackjs besser?
inf3rno
2
@ inf3rno Es ist mehr als eine Stapelverfolgung, die nur den Beginn des Fehlers verfolgt. TrackJS verfolgt die gesamte Benutzersitzung, sodass Sie sehen können, was dazu geführt hat. Hier ist ein Screenshot als Beispiel trackjs.com/assets/images/screenshot.png
Kane
1

Sie hören das Ereignis onerror ab, indem Sie window.onerror eine Funktion zuweisen:

 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
quelle