JavaScript mekanisme acara global

350

Saya ingin menangkap setiap kesalahan fungsi yang tidak terdefinisi. Apakah ada fasilitas penanganan kesalahan global dalam JavaScript? Kasus penggunaan menangkap panggilan fungsi dari flash yang tidak ditentukan.

Brian Tompsett - 汤 莱恩
sumber
Apa yang ingin Anda lakukan dengan kesalahan setelah Anda menangkapnya? Apakah Anda hanya perlu login sehingga Anda dapat membuat fungsi yang hilang, atau Anda ingin menghentikan pengecualian dari melanggar kode Anda?
Dan Herbert
2
Saya ingin mendapatkan nama fungsi yang hilang dipanggil dan berdasarkan keberadaan beberapa string memanggil fungsi saya sendiri. Setiap panggilan ke fungsi dengan string 'tutup' akan memanggil penutupan saya () misalnya. Saya juga ingin menjebak kesalahan pada saat itu.
2
exceptionionsjs.com menyediakan fungsionalitas ini dan hanya dapat disadap untuk menangkap kesalahan yang terkait dengan fungsionalitas yang tidak terdefinisi dengan fungsionalitas "penjaga" nya.
Steven Wexler

Jawaban:

193

Apakah ini membantu Anda:

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

xxx();
</script>

Saya tidak yakin bagaimana menangani kesalahan Flash ...

Pembaruan: tidak berfungsi di Opera, tapi saya meretas Dragonfly sekarang untuk melihat apa yang didapatnya. Saran tentang peretasan Dragonfly datang dari pertanyaan ini:

Jendela Mimic. onerror di Opera menggunakan javascript

Ionuț G. Stan
sumber
3
Dengan penambahan msg, file_loc, line_no params ini harus dilakukan untuk saya. Terima kasih!
1
Apakah saya mengerti benar bahwa kode ini menimpa penangan kesalahan yang ada?
Mars Robertson
@MarsRobertson No.
atilkan
@atilkan window.onerror = function() { alert(42) };sekarang kode dalam jawaban: window.onerror = function() { alert("Error caught"); };tidak diganti, saya masih tidak yakin ..
Mars Robertson
@ MarsRobertson saya mengerti. Mungkin menimpa. Ya, benar, baru diuji. Menggunakan addEventListener akan lebih baik.
atilkan
646

Cara Menangkap Kesalahan Javascript Tidak Tertangani

Tetapkan window.onerroracara ke pengendali acara seperti:

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

Seperti yang dikomentari dalam kode, jika nilai baliknya window.onerroradalah truemaka browser harus menekan menampilkan dialog peringatan.

Kapan acara window.onerror Fire?

Singkatnya, acara dinaikkan ketika 1.) ada pengecualian atau 2.) kesalahan waktu kompilasi terjadi.

pengecualian tanpa tertangkap

  • melempar "beberapa pesan"
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document ;, pengecualian keamanan

kompilasi kesalahan

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, ia akan mencoba untuk mengkompilasi argumen pertama sebagai skrip

Browser mendukung window.onerror

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

Tangkapan layar:

Contoh kode onerror di atas dalam tindakan setelah menambahkan ini ke halaman pengujian:

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

Peringatan Javascript menunjukkan informasi kesalahan yang dirinci oleh peristiwa window.onerror

Contoh untuk pelaporan kesalahan 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/

Referensi:

Sam
sumber
3
layak disebutkan firefox tidak memberikan kembali pesan kesalahan saat throwdibuat secara manual. stackoverflow.com/questions/15036165/…
Sebas
2
Jawaban yang bagus Anda dapat menerapkan "Laporkan kesalahan ini melalui ajax" dengan paket seperti JSNLog, yang melakukan ajax dan pencatatan sisi server untuk Anda.
user1147862
4
Selain jawaban ini, saya menambahkan objek err sehingga saya bisa mendapatkan jejak stack. stackoverflow.com/a/20972210/511438 . Sekarang saya dapat mengembangkan dengan lebih banyak umpan balik karena kesalahan dev saya muncul sebagai kotak di bagian atas halaman (seperti yang saya buat).
Valamas
Bukankah lebih baik untuk mengemas implementasi onerror Anda di blok try-catch (abaikan), mencegah onerror Anda () untuk membuat lebih banyak kesalahan? (bukan itu masalahnya di sini, tetapi hanya untuk memastikan)
Pieter De Bie
1
@ super1ha1 Bisakah Anda menyediakan jsfiddle? Saya tidak berpikir browser akan memperkenalkan perubahan di mana pengendali event onerror berhenti menembakkan acara. Anda dapat mencoba jsfiddle ini untuk melihat penangan onerror berfungsi: jsfiddle.net/nzfvm44d Ini masih berfungsi untuk saya di Chrome versi 62.0.3202.94 (Pembuatan Resmi) (64-bit).
Sam
38

penanganan kesalahan yang canggih

Jika penanganan kesalahan Anda sangat canggih dan karena itu mungkin menimbulkan kesalahan itu sendiri, ada baiknya menambahkan tanda yang menunjukkan jika Anda sudah dalam "errorHandling-Mode". Seperti itu:

var appIsHandlingError = false;

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

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

Kalau tidak, Anda bisa menemukan diri Anda dalam loop tak terbatas.

SunnyRed
sumber
29
Atau cara yang lebih aman adalah menggunakan try-catch di sekitar handleErrormetode.
Aidiakapi
8
Anda tidak dapat menggunakan try catch jika Anda memiliki panggilan tidak sinkron dalam penanganan kesalahan Anda. Jadi solusinya tetap merupakan solusi yang baik
Emrys Myrooin
@EmrysMyrooin Tetap itu akan menyebabkan sinkronisasi atau loop async oleh penanganan kesalahan dan mungkin macet tanpa info tumpukan yang dapat digunakan.
inf3rno
1
Saya pikir bendera harus diatur ulang di beberapa titik, bukan? Saya percaya kita perlu mencoba / menangkap jika besar jika, dan pastikan bendera diatur ulang tepat sebelum meninggalkan metode onerror. Kalau tidak, hanya satu kesalahan yang akan ditangani.
Seb
23

Coba Atatus yang menyediakan Pelacakan Kesalahan Tingkat Lanjut dan Pemantauan Pengguna Nyata untuk aplikasi web modern.

https://www.atatus.com/

Biarkan saya menjelaskan cara mendapatkan stacktraces yang cukup lengkap di semua browser.

Kesalahan penanganan dalam JavaScript

Modern Chrome dan Opera sepenuhnya mendukung spesifikasi draft HTML 5 untuk ErrorEvent dan window.onerror. Di kedua browser ini, Anda dapat menggunakan window.onerror, atau mengikat acara 'kesalahan' dengan benar:

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

Sayangnya Firefox, Safari dan IE masih ada dan kami harus mendukungnya juga. Karena stacktrace tidak tersedia, window.onerrorkita harus melakukan sedikit lebih banyak pekerjaan.

Ternyata satu-satunya hal yang bisa kita lakukan untuk mendapatkan stacktraces dari kesalahan adalah dengan membungkus semua kode kita dalam sebuah try{ }catch(e){ }blok dan kemudian melihatnya e.stack. Kita dapat membuat prosesnya lebih mudah dengan fungsi yang disebut wrap yang mengambil fungsi dan mengembalikan fungsi baru dengan penanganan kesalahan yang baik.

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

Ini bekerja. Setiap fungsi yang Anda bungkus secara manual akan memiliki penanganan kesalahan yang baik, tetapi ternyata kami benar-benar dapat melakukannya untuk Anda secara otomatis dalam banyak kasus.

Dengan mengubah definisi global addEventListenersehingga secara otomatis membungkus panggilan balik kita dapat secara otomatis memasukkan try{ }catch(e){ }sekitar sebagian besar kode. Ini memungkinkan kode yang ada terus berfungsi, tetapi menambahkan pelacakan pengecualian berkualitas tinggi.

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

Kita juga harus memastikan itu removeEventListenertetap bekerja. Saat ini tidak akan karena argumen untuk addEventListenerdiubah. Sekali lagi kita hanya perlu memperbaiki ini pada prototypeobjek:

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

Kirimkan data kesalahan ke backend Anda

Anda dapat mengirim data kesalahan menggunakan tag gambar sebagai berikut

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

Penafian: Saya seorang pengembang web di https://www.atatus.com/ .

Fizer Khan
sumber
Apa yourserver.com/jserror ? REST, Layanan Web, Layanan Wcf ? Adakah yang sederhana tentang backend?
Kiquenet
Jika Anda ingin mengirim kesalahan js dari browser pengguna ke server Anda. Anda harus menulis backend Anda ( http://yourserver.com) untuk menerima dan menyimpan. Jika Anda memilih atatus.com , Anda tidak perlu melakukan apa pun. Cukup sertakan dua baris skrip di halaman Anda.
Fizer Khan
18

Tampaknya window.onerrortidak memberikan akses ke semua kemungkinan kesalahan. Secara khusus itu mengabaikan:

  1. <img> memuat kesalahan (respons> = 400).
  2. <script> memuat kesalahan (respons> = 400).
  3. kesalahan global jika Anda memiliki banyak perpustakaan lain di aplikasi Anda yang juga memanipulasi window.onerrordengan cara yang tidak diketahui (jquery, angular, dll.).
  4. mungkin banyak kasus yang belum saya temui setelah menjelajahi ini sekarang (iframe, stack overflow, dll.).

Inilah awal skrip yang menangkap banyak kesalahan ini, sehingga Anda dapat menambahkan lebih banyak debugging yang kuat ke aplikasi Anda selama pengembangan.

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

})();

Ini bisa digunakan seperti ini:

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

Script lengkap memiliki implementasi default yang mencoba untuk mencetak versi "display" semi-readable dari entitas / kesalahan yang diterimanya. Dapat digunakan untuk inspirasi bagi penangan kesalahan khusus aplikasi. Implementasi default juga menyimpan referensi ke 100 entitas kesalahan terakhir, sehingga Anda dapat memeriksanya di konsol web setelah terjadi seperti:

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

Catatan: Ini berfungsi dengan mengganti metode pada beberapa peramban / konstruktor asli. Ini dapat memiliki efek samping yang tidak diinginkan. Namun, telah berguna untuk digunakan selama pengembangan, untuk mencari tahu di mana kesalahan terjadi, untuk mengirim log ke layanan seperti NewRelic atau Sentry selama pengembangan sehingga kami dapat mengukur kesalahan selama pengembangan, dan pada pementasan sehingga kami dapat men-debug apa yang sedang terjadi di tingkat yang lebih dalam. Kemudian dapat dimatikan dalam produksi.

Semoga ini membantu.

Lance Pollard
sumber
1
Rupanya gambar memicu peristiwa kesalahan: 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
sumber
6

Seseorang harus mempertahankan callback onerror sebelumnya terkait juga

<script type="text/javascript">

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

</script>
Apoorv Saxena
sumber
3

Jika Anda ingin cara terpadu untuk menangani kesalahan yang tidak tertangkap dan penolakan janji yang tidak ditangani, Anda dapat melihat di perpustakaan yang tidak tertangkap .

EDIT

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

Itu mendengarkan jendela. penolakan unhandled selain window.onerror.

Aleksandr Oleynikov
sumber
2
Lance Pollard dalam jawaban di atas menyebutkan beberapa kesalahan, yang tidak ditangani oleh normal onerror. Apakah perpustakaan Anda menangani mereka? Jika beberapa dari mereka, sebutkan yang mana?
Michael Freidgeim
@ MiFreidgeimSO-stopbeingevil Baru saja memeriksa kode sumber - sayangnya tidak. Aleksandr Oleynikov: Akan luar biasa jika Anda dapat mendukung ini - Saya kemudian akan mengubah downvote saya menjadi upvote ;-)
brillout
2

Saya akan merekomendasikan mencoba Trackjs .

Galat saat masuk sebagai layanan.

Sangat sederhana untuk diatur. Cukup tambahkan satu baris <script> ke setiap halaman dan hanya itu. Ini juga berarti akan sangat mudah untuk dihapus jika Anda memutuskan tidak menyukainya.

Ada layanan lain seperti Sentry (yang merupakan open-source jika Anda dapat meng-host server Anda sendiri), tetapi tidak melakukan apa yang Trackjs lakukan. Trackjs merekam interaksi pengguna antara browser mereka dan server web Anda sehingga Anda benar-benar dapat melacak langkah-langkah pengguna yang menyebabkan kesalahan, bukan hanya referensi file dan nomor baris (dan mungkin stack trace).

kane
sumber
2
TrackJS tampaknya tidak memiliki tingkat gratis lagi, meskipun ada uji coba gratis.
pkaeding
Ini bagus untuk melacak dan diberitahu akan adanya kesalahan tetapi tidak benar-benar menyelesaikan bagian penanganan. Idealnya saya pikir penanya mencari cara untuk menangani kesalahan ini sehingga sisa kode masih berjalan.
wgp
Bagaimana jika Anda menangkap peristiwa kesalahan dan menambahkan panggilan xhr ke logger dengan jejak stack dan status aplikasi? Bagaimana trackj lebih baik?
inf3rno
2
@ inf3rno ini lebih dari sekedar tumpukan jejak, yang hanya melacak awal kesalahan. TrackJS akan melacak seluruh sesi pengguna sehingga Anda dapat melihat apa yang menyebabkannya. inilah tangkapan layar sebagai contoh trackjs.com/assets/images/screenshot.png
kane
1

Anda mendengarkan acara onerror dengan menetapkan fungsi ke 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
sumber