Globalny mechanizm zdarzeń JavaScript

350

Chciałbym wyłapać każdy zgłoszony błąd niezdefiniowanej funkcji. Czy w JavaScript jest dostępna funkcja globalnej obsługi błędów? Przypadek użycia przechwytuje wywołania funkcji z pamięci flash, które nie są zdefiniowane.

Brian Tompsett - 汤 莱恩
źródło
Co chcesz zrobić z błędem po jego złapaniu? Czy po prostu musisz go zalogować, aby utworzyć brakującą funkcję, czy chcesz powstrzymać wyjątki od łamania kodu?
Dan Herbert
2
Chciałbym uzyskać nazwę brakującej funkcji i na podstawie obecności jakiegoś ciągu wywołać moją własną funkcję. Każde wywołanie funkcji z ciągiem „close” wywołałoby na przykład funkcję my close (). Chciałbym również złapać błąd w tym momencie.
2
exceptionsjs.com zapewnia tę funkcję i może być taylored jedynie błędów połowowych związanych z nieokreślonym funkcjonalności z jego „strażnika” funkcjonalności.
Steven Wexler,

Odpowiedzi:

192

Czy to pomaga ci:

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

xxx();
</script>

Nie jestem jednak pewien, jak radzi sobie z błędami Flasha ...

Aktualizacja: nie działa w Operze, ale teraz hakuję Dragonfly, aby zobaczyć, co dostaje. Sugestia dotycząca hakowania Dragonfly pochodzi z tego pytania:

Okno naśladować. onerror w Operze przy użyciu javascript

Ionuț G. Stan
źródło
3
Po dodaniu parametrów msg, file_loc, line_no powinno to zrobić dla mnie. Dzięki!
1
Czy rozumiem poprawnie, że ten kod zastępuje istniejące programy obsługi błędów?
Mars Robertson
@MarRobertson No.
atilkan
@atilkan window.onerror = function() { alert(42) };teraz kod w odpowiedzi: window.onerror = function() { alert("Error caught"); };nie przesłonięty, wciąż nie jestem pewien ..
Mars Robertson
@MarsRobertson Rozumiem. Prawdopodobnie nadpisuje. Tak, właśnie przetestowane. Lepiej byłoby użyć addEventListener.
atilkan
646

Jak wyłapywać nieobsługiwane błędy JavaScript

Przypisz window.onerrorzdarzenie do procedury obsługi zdarzenia, takiej jak:

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

Jak skomentowano w kodzie, jeśli zwróconą wartością window.onerrorjest, trueto przeglądarka powinna ukryć wyświetlanie okna dialogowego z ostrzeżeniem.

Kiedy uruchamia się zdarzenie window.onerror Event?

W skrócie, zdarzenie jest wywoływane, gdy 1.) wystąpi nieprzechwycony wyjątek lub 2.) wystąpi błąd czasu kompilacji.

nieprzechwycone wyjątki

  • rzuć „niektóre wiadomości”
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document ;, wyjątek bezpieczeństwa

błąd kompilacji

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, spróbuje skompilować pierwszy argument jako skrypt

Przeglądarki obsługujące window.onerror

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

Zrzut ekranu:

Przykład powyższego kodu onerror w akcji po dodaniu go do strony testowej:

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

Alert JavaScript pokazujący informacje o błędach wyszczególnione przez zdarzenie window.onerror

Przykład raportowania błędów 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/

Bibliografia:

Sam
źródło
3
warto wspomnieć, że Firefox nie wyświetla komunikatu o błędzie, gdy throwjest wykonywany ręcznie. stackoverflow.com/questions/15036165/…
Sebas
2
Świetna odpowiedź. Możesz zaimplementować „Zgłoś ten błąd przez ajax” za pomocą pakietu takiego jak JSNLog, który wykonuje dla Ciebie logowanie po stronie ajax i serwer.
user1147862,
4
Oprócz tej odpowiedzi dodałem obiekt err, aby uzyskać ślad stosu. stackoverflow.com/a/20972210/511438 . Teraz mogę opracowywać z większą ilością opinii, ponieważ moje błędy deweloperów pojawiają się w polu u góry strony (tak jak je utworzyłem).
Valamas
Czy nie byłoby lepiej spakować swoją implementację onerror w bloku try-catch (ignore), aby zapobiec sytuacji, w której onerror () generowałby jeszcze więcej błędów? (nie żeby tak było, ale dla pewności)
Pieter De Bie
1
@ super1ha1 Czy możesz podać jsfiddle? Nie sądzę, aby przeglądarki wprowadziły przełomową zmianę, w której program obsługi zdarzeń onerror przestałby uruchamiać zdarzenia. Możesz wypróbować ten jsfiddle, aby zobaczyć działający program obsługi onerror: jsfiddle.net/nzfvm44d To nadal działa dla mnie w wersji Chrome 62.0.3202.94 (oficjalna wersja) (64-bit).
Sam
38

wyrafinowana obsługa błędów

Jeśli obsługa błędów jest bardzo wyrafinowana i dlatego może sam wygenerować błąd, przydatne jest dodanie flagi wskazującej, czy użytkownik znajduje się już w „trybie obsługi błędów”. Tak jak:

var appIsHandlingError = false;

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

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

W przeciwnym razie możesz znaleźć się w nieskończonej pętli.

SunnyRed
źródło
29
Lub bardziej niezawodny sposób byłoby użycie try-catch wokół tej handleErrormetody.
Aidiakapi
8
Nie możesz użyć try catch, jeśli masz połączenie asynchroniczne podczas obsługi błędów. Więc rozwiązanie pozostaje dobrym rozwiązaniem
Emrys Myrooin
@EmrysMyrooin Nadal spowoduje synchronizację lub pętlę asynchroniczną przez obsługę błędów i prawdopodobnie ulegnie awarii bez informacji o stosie.
inf3rno
1
Myślę, że flaga powinna zostać zresetowana w pewnym momencie, prawda? Uważam, że potrzebujemy spróbować / złapać, jeśli jest duży, i upewnij się, że flaga została zresetowana tuż przed opuszczeniem metody onerror. W przeciwnym razie obsłużony zostanie tylko jeden błąd.
Seb
23

Wypróbuj Atatus, który zapewnia zaawansowane śledzenie błędów i monitorowanie rzeczywistych użytkowników w nowoczesnych aplikacjach internetowych.

https://www.atatus.com/

Pozwól, że wyjaśnię, jak uzyskać ślady stosu, które są w miarę kompletne we wszystkich przeglądarkach.

Błąd obsługi w JavaScript

Nowoczesne Chrome i Opera w pełni obsługują specyfikację wersji roboczej HTML 5 dla ErrorEvent i window.onerror. W obu tych przeglądarkach można odpowiednio użyć window.onerrorlub powiązać zdarzenie „błąd”:

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

Niestety Firefox, Safari i IE są nadal dostępne i my też musimy je wspierać. Ponieważ stacktrace nie jest dostępny wwindow.onerror musimy wykonać trochę więcej pracy.

Okazuje się, że jedyne, co możemy zrobić, aby uzyskać ślady stosu z błędów, to owinąć cały nasz kod w try{ }catch(e){ }blok, a następnie spojrzeć e.stack. Możemy nieco ułatwić ten proces dzięki funkcji o nazwie wrap, która przyjmuje funkcję i zwraca nową funkcję z dobrą obsługą błędów.

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

To działa. Każda funkcja, którą ręcznie zawijasz, będzie dobrze obsługiwać błędy, ale okazuje się, że w większości przypadków możemy to zrobić automatycznie.

Zmieniając globalną definicję addEventListenertak, aby automatycznie zawijała wywołanie zwrotne, możemy automatycznie wstawiać try{ }catch(e){ }większość kodu. Dzięki temu istniejący kod nadal działa, ale dodaje wysokiej jakości śledzenie wyjątków.

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

Musimy również upewnić się, że removeEventListenernadal działa. W tej chwili tak się nie stanie, ponieważ argument do addEventListenerzostał zmieniony. Znów musimy tylko to naprawić na prototypeobiekcie:

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

Prześlij dane błędu do swojego zaplecza

Dane o błędach można wysyłać za pomocą znacznika obrazu w następujący sposób

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

Oświadczenie: Jestem programistą internetowym na https://www.atatus.com/ .

Fizer Khan
źródło
Co to jest yourserver.com/jserror ? REST, usługa sieci Web, usługa Wcf ? Jakieś proste informacje o backendu?
Kiquenet
Jeśli chcesz wysłać błędy js z przeglądarki użytkownika na swój serwer. musisz napisać backend ( http://yourserver.com), aby odbierać i przechowywać. Jeśli wybierzesz atatus.com , nie musisz nic robić. Wystarczy dołączyć dwie linie skryptu na swojej stronie.
Fizer Khan,
18

Wygląda na to, że window.onerrornie zapewnia dostępu do wszystkich możliwych błędów. W szczególności ignoruje:

  1. <img> błędy ładowania (odpowiedź> = 400).
  2. <script> błędy ładowania (odpowiedź> = 400).
  3. globalne błędy, jeśli masz wiele innych bibliotek w swojej aplikacji, które również manipulują window.onerrorw nieznany sposób (jquery, angular, itp.).
  4. prawdopodobnie w wielu przypadkach nie natknąłem się na to po zbadaniu tego (iframe, przepełnienie stosu itp.).

Oto początek skryptu, który wyłapuje wiele z tych błędów, dzięki czemu możesz dodać bardziej niezawodne debugowanie do swojej aplikacji podczas programowania.

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

})();

Można go użyć w następujący sposób:

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

Pełny skrypt ma domyślną implementację, która próbuje wydrukować częściowo czytelną „wyświetlaną” wersję otrzymanego obiektu / błędu. Może być wykorzystany jako inspiracja do obsługi błędów specyficznych dla aplikacji. Domyślna implementacja zachowuje również odwołanie do ostatnich 100 jednostek błędów, dzięki czemu można je sprawdzić w konsoli internetowej po ich wystąpieniu, np .:

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

Uwaga: Działa to poprzez zastąpienie metod w kilku konstruktorach przeglądarki / natywnych. Może to mieć niezamierzone skutki uboczne. Przydało się to jednak podczas programowania, aby dowiedzieć się, gdzie występują błędy, aby wysłać dzienniki do usług takich jak NewRelic lub Sentry podczas programowania, abyśmy mogli mierzyć błędy podczas programowania oraz podczas przemieszczania, abyśmy mogli debugować to, co się dzieje głębszy poziom. Następnie można go wyłączyć w produkcji.

Mam nadzieję że to pomoże.

Lance Pollard
źródło
1
Najwyraźniej obrazy wyzwalają zdarzenie błędu: 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
źródło
6

Należy również zachować wcześniej skojarzone wywołanie zwrotne onerror

<script type="text/javascript">

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

</script>
Apoorv Saxena
źródło
3

Jeśli chcesz ujednoliconego sposobu obsługi zarówno nieprzechwyconych błędów, jak i nieobsługiwanych odrzuceń obietnic, możesz zajrzeć do nieprzechwyconej biblioteki .

EDYTOWAĆ

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

Słucha okna. nieobsługiwane odrzucenie oprócz window.onerror.

Aleksandr Oleynikov
źródło
2
Lance Pollard w powyższej odpowiedzi wspomniał o kilku błędach, których normalny onerror nie obsługuje. Czy biblioteka je obsługuje? Jeśli niektóre z nich, proszę podać które?
Michael Freidgeim
@ MiFreidgeimSO-stopbeingevil Właśnie sprawdziłem kod źródłowy - niestety nie. Aleksandr Oleynikov: Byłoby wspaniale, gdybyś mógł to wesprzeć - wtedy zmienię zdanie na głosowanie ;-)
brillout
2

Polecam wypróbować Trackjs .

To rejestrowanie błędów jako usługa.

Jest niezwykle prosty w konfiguracji. Wystarczy dodać jeden wiersz <script> do każdej strony i to wszystko. Oznacza to również, że będzie niezwykle łatwy do usunięcia, jeśli zdecydujesz, że ci się nie podoba.

Istnieją inne usługi, takie jak Sentry (który jest open-source, jeśli możesz hostować własny serwer), ale nie robi to, co robi Trackjs. Trackjs rejestruje interakcję użytkownika między jego przeglądarką a serwerem WWW, dzięki czemu można faktycznie śledzić kroki użytkownika, które doprowadziły do ​​błędu, w przeciwieństwie do samego odwołania do pliku i numeru linii (i może śledzenia stosu).

Kane
źródło
2
Wygląda na to, że TrackJS nie ma już wolnego poziomu , chociaż istnieje bezpłatny okres próbny.
pkaeding
Jest to przydatne do śledzenia i powiadamiania o błędach, ale tak naprawdę nie rozwiązuje części obsługi. Idealnie myślę, że pytający szuka sposobu na poradzenie sobie z tymi błędami, aby reszta kodu nadal działała.
wgp
Co się stanie, jeśli złapiesz zdarzenie błędu i dodasz wywołanie xhr do programu rejestrującego ze śledzeniem stosu i stanem aplikacji? W jaki sposób trackjs jest lepszy?
inf3rno
2
@ inf3rno to coś więcej niż ślad stosu, który tylko śledzi początek błędu. TrackJS będzie śledził całą sesję użytkownika, abyś mógł zobaczyć, co do niej doprowadziło. Oto zrzut ekranu jako przykład trackjs.com/assets/images/screenshot.png
kane
1

Słuchasz zdarzenia onerror, przypisując funkcję do 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
źródło