Capture and report JavaScript errors with window.onerror
原文:https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror.html
onerror is a special browser event that fires whenever an uncaught JavaScript error has been thrown. It’s one of the easiest ways to log client-side errors and report them to your servers. It’s also one of the major mechanisms by which Sentry’s client JavaScript integration (raven-js) works.
You listen to the onerror event by assigning a function to window.onerror:
window.onerror = function (msg, url, lineNo, columnNo, error) {
// ... handle error ...
return false;
}
When an error is thrown, the following arguments are passed to the function:
- msg – The message associated with the error, e.g. “Uncaught ReferenceError: foo is not defined”
- url – The URL of the script or document associated with the error, e.g. “/dist/app.js”
- lineNo – The line number (if available)
- columnNo – The column number (if available)
- error – The Error object associated with this error (if available)
The first four arguments tell you in which script, line, and column the error occurred. The final argument, Error object, is perhaps the most valuable. Let’s learn why.
The Error object and error.stack
At first glance the Error object isn’t very special. It contains 3 standardized properties: message, fileName, and lineNumber. Redundant values that already provided to you via window.onerror.
The valuable part is a non-standard property: Error.prototype.stack. This stack property tells you at what source location each frame of the program was when the error occurred. The stack trace can be a critical part of debugging an error. And despite being non-standard, this property is available in every modern browser.
Here’s an example of the Error object’s stack property in 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)"
Hard to read, right? The stack property is actually just an unformatted string.
Here’s what it looks like formatted:
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)
Once it’s been formatted, it’s easy to see how the stack property can be critical in helping to debug an error.
There’s just one snag: the stack property is non-standard, and its implementation differs among browsers. For example, here’s the same stack trace from 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)
Not only is the format of each frame different, the frames also have less detail. For example, Chrome identifies that the new keyword has been used, and has greater insight into eval invocations. And this is just IE 11 vs Chrome – other browsers similar have varying formats and detail.
Luckily, there are tools out there that normalize stack properties so that it is consistent across browsers. For example, raven-js uses TraceKit to normalize error strings. There’s also stacktrace.js and a few other projects.
Browser compatibility
window.onerror has been available in browsers for some time – you’ll find it in browsers as old as IE6 and Firefox 2.
The problem is that every browser implements window.onerror differently. Particularly, in how many arguments are sent to to the onerror listener, and the structure of those arguments.
Here’s a table of which arguments are passed to onerror in most browsers:
| Browser | Message | URL | lineNo | colNo | errorObj |
|---|---|---|---|---|---|
| Firefox 42 | ✓ | ✓ | ✓ | ✓ | ✓ |
| Chrome 46 | ✓ | ✓ | ✓ | ✓ | ✓ |
| Android Browser 4.4 | ✓ | ✓ | ✓ | ✓ | |
| Edge | ✓ | ✓ | ✓ | ✓ | |
| IE 11 | ✓ | ✓ | ✓ | ✓ | ✓ |
| IE 10 | ✓ | ✓ | ✓ | ✓ | |
| IE 9, 8 | ✓ | ✓ | ✓ | ||
| Safari 9 | ✓ | ✓ | ✓ | ✓ | |
| iOS 9 | ✓ | ✓ | ✓ | ✓ |
You’ll notice that the latest Apple browsers – Safari and iOS – don’t support a 5th error object argument. And while the final version of Internet Explorer (11) supports the error object, Microsoft’s latest browser, Edge, does not.
Without the error object, there is no stack trace property. This means that these browsers cannot retrieve valuable stack information from errors caught by onerror.
Polyfilling window.onerror with try/catch
But there is a workaround – you can wrap code in your application inside a try/catch and catch the error yourself. This error object will contain our coveted stack property in every modern browser.
Consider the following helper method, invoke, which calls a function on an object with an array of arguments:
function invoke(obj, method, args) {
return obj[method].apply(this, args);
}
invoke(Math, 'max', [1, 2]); // returns 2
Here’s invoke again, this time wrapped in try/catch, in order to capture any thrown error:
function invoke(obj, method, args) {
try {
return obj[method].apply(this, args);
} catch (e) {
captureError(e); // report the error
throw e; // re-throw the error
}
}
invoke(Math, 'highest', [1, 2]); // throws error, no method Math.highest
Of course, doing this manually everywhere is pretty cumbersome. You can make it easier by creating a generic wrapper utility function:
function wrapErrors(fn) {
// don't wrap function more than once
if (!fn.__wrapped__) {
fn.__wrapped__ = function () {
try {
return fn.apply(this, arguments);
} catch (e) {
captureError(e); // report the error
throw e; // re-throw the error
}
};
}
return fn.__wrapped__;
}
var invoke = wrapErrors(function(obj, method, args) {
return obj[method].apply(this, args);
});
invoke(Math, 'highest', [1, 2]); // no method Math.highest
Because JavaScript is single threaded, you don’t need to use wrap everywhere – just at the beginning of every new stack.
That means you’ll need to wrap function declarations:
- At the start of your application (e.g. in
$(document).readyif you use jQuery) - In event handlers, e.g.
addEventListeneror$.fn.click - Timer-based callbacks, e.g.
setTimeoutorrequestAnimationFrame
For example:
$(wrapErrors(function () { // application start
doSynchronousStuff1(); // doesn't need to be wrapped
setTimeout(wrapErrors(function () {
doSynchronousStuff2(); // doesn't need to be wrapped
});
$('.foo').click(wrapErrors(function () {
doSynchronousStuff3(); // doesn't need to be wrapped
});
}));
If that seems like a heck of a lot of work, don’t worry! Most error reporting libraries have mechanisms for augmenting built-in functions like addEventListener and setTimeout so that you don’t have to call a wrapping utility every time yourself. And yes, raven-js does this too.
Transmitting the error to your servers
Okay, so you’ve done your job – you’ve plugged into window.onerror, and you’re additionally wrapping functions in try/catch in order to catch as much error information as possible.
There’s just one last step: transmitting the error information to your servers. In order for this to work, you’ll need to set up some kind of reporting web service that will accept your error data over HTTP, log it to a file and/or store it in a database.
If this web service is on the same domain as your web application, this is achieved easily by using XMLHttpRequest. In the example below, we use jQuery’s AJAX function to transmit the data to our servers:
function captureError(ex) {
var errorData = {
name: ex.name, // e.g. ReferenceError
message: ex.line, // e.g. x is undefined
url: document.location.href,
stack: ex.stack // stacktrace string; remember, different per-browser!
};
$.post('/logger/js/', {
data: errorData
});
}
Note that if you have to transmit your error across different origins, your reporting endpoint will need to support CORS (Cross Origin Resource Sharing).
Summary
If you’ve made it this far, you now have all the tools you need to roll your own basic error reporting library and integrate it with your application:
- How
window.onerrorworks, and what browsers it supports - How to use try/catch to capture stack traces where window.onerror is lacking
- Transmitting error data to your servers
Of course, if you don’t want to bother with all of this, there are plenty of commercial and open source tools that do all the heavy-lifting of client-side reporting for you. (Psst, you might want to try Sentry.)
That’s it! Happy error hunting.
Capture and report JavaScript errors with window.onerror的更多相关文章
- 【转】window.onerror跨域问题
What the heck is "Script error"? Ben Vinegar/ May 17, 2016 If you’ve done any work with th ...
- How can we listen for errors that do not trigger window.onerror?
原文: http://stackoverflow.com/questions/19141195/how-can-we-listen-for-errors-that-do-not-trigger-win ...
- window.onerror事件用来自定义错误处理
Event reference: https://developer.mozilla.org/en-US/docs/Web/Events http://w3c.github.io/html/ ...
- 用Google Analytics跟踪JavaScript Errors (译)
通过custom events来实施 // Track basic JavaScript errors window.addEventListener('error', function(e) { _ ...
- (转载)JavaScript中的Window窗口对象
(转载)http://www.ijavascript.cn/jiaocheng/javascript-window-65.html 例子: <html> <head> < ...
- window.onerror 应用实例
详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp75 window.onerror = function(sMessa ...
- [译]window.onerror事件
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- JavaScript权威指南--window对象
知识要点 window对象及其客户端javascript所扮演的核心角色:它是客户端javascript程序的全局对象.本章介绍window对象的属性和方法,这些属性定义了不同的API,但是只有一部分 ...
- Top 10 JavaScript errors
Top 10 JavaScript errors javascript errors https://rollbar.com/blog/tags/top-errors https://rollbar. ...
随机推荐
- TebsorFlow低阶API(五)—— 保存和恢复
简介 tf.train.Saver 类提供了保存和恢复模型的方法.通过 tf.saved_model.simple_save 函数可以轻松地保存适合投入使用的模型.Estimator会自动保存和恢复 ...
- dialog - 从 shell 显示对话框
总览 (SYNOPSIS) dialog --clear dialog --create-rc file dialog --print-maxsize dialog common-options bo ...
- autoOpenBrowser: true, 运行npm后自动打开浏览器
autoOpenBrowser: true, 运行npm后自动打开浏览器
- HNOI2006 潘多拉的盒子
题目描述 题解: 题目的描述比较长,理解起来也有一定难度.仔细读题后我们发现整个任务可以分成两个部分:找出咒语机之间所有的升级关系.求最长升级序列. 1. 求升级关系: 容易看出,咒语机i可以抽象成一 ...
- [LUOGU] [NOIP2017] P3960 列队
题目描述 Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有 n \times mn×m 名学生,方阵的行 ...
- 生产环境屏蔽swagger(动态组装bean)
spring动态组装bean 背景介绍: 整合swagger时需要在生产环境中屏蔽掉swagger的地址,不能在生产环境使用 解决方案 使用动态profile在生产环境中不注入swagger的bean ...
- (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
openssl rsa 是RSA对称密钥的处理工具 openssl pkey 是通用非对称密钥处理工具,它们用法基本一致,所以只举例说明openssl rsa. 它们的用法很简单,基 ...
- l5-repository基本使用--结合使用artisan
一.从头开始创建 1.执行以下artisan: php artisan make:entity Student 如果某个文件已经存在,则不会创建新的文件去覆盖原有的文件,案例如下: 2.修改model ...
- Python装饰器粗解学习
此次学习资料详细来自:http://blog.csdn.net/u013471155 本次是粗学,仍有诸多疑问,暂且记录一二,如有不足和建议,希望可以达者指点. 三个关键点理解: 1.关于函数“变 ...
- 一份快速实用的 tcpdump 命令参考手册
对于 tcpdump 的使用,大部分管理员会分成两类.有一类管理员,他们熟知 tcpdump 和其中的所有标记:另一类管理员,他们仅了解基本的使用方法,剩下事情都要借助参考手册才能完成.出现这种情况 ...