本文是与Sentry合作创建的。 感谢您支持使SitePoint成为可能的合作伙伴。
onerror
是一个特殊的浏览器事件,每当引发未捕获的JavaScript错误时都会触发。 这是记录客户端错误并将其报告给服务器的最简单方法之一。 它也是Sentry的客户端JavaScript集成 (raven-js)工作的主要机制之一。
您通过向window.onerror
分配一个函数来监听onerror事件:
window.onerror = function (msg, url, lineNo, columnNo, error) {
// ... handle error ...
return false;
}
引发错误时,会将以下参数传递给该函数:
- msg –与错误关联的消息,例如“ Uncaught ReferenceError:未定义foo”
- url –与错误关联的脚本或文档的URL,例如“ /dist/app.js”
- lineNo –行号(如果有)
- columnNo –列号(如果有)
- 错误 –与该错误关联的Error对象(如果有)
前四个参数告诉您发生错误的脚本,行和列。 最后一个参数Error对象也许是最有价值的。 让我们学习为什么。
错误对象和error.stack
乍一看,Error对象不是很特殊。 它包含3个标准化的属性: message , fileName和lineNumber 。 已经通过window.onerror
提供给您的冗余值。
有价值的部分是非标准属性: Error.prototype.stack
。 此堆栈属性告诉您错误发生时程序的每个帧位于哪个源位置。 错误堆栈跟踪可能是调试的关键部分。 并且尽管是非标准的,但该属性在每种现代浏览器中都可用。
这是Chrome 46中Error对象的stack属性的示例:
"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)"
很难读,对不对? stack属性实际上只是一个未格式化的字符串。
格式如下:
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)
格式化之后,就很容易看出堆栈属性在帮助调试错误中的重要性。
只有一个障碍:stack属性是非标准的,并且其实现在浏览器之间也有所不同。 例如,这是来自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)
不仅每个帧的格式不同,而且帧的细节也更少。 例如,Chrome可以识别出已使用了new
关键字,并且可以更深入地了解eval
调用。 这只是IE 11与Chrome的对比-其他浏览器的格式和细节也有所不同。
幸运的是,那里有一些工具可以标准化堆栈属性,以便在浏览器之间保持一致。 例如,raven-js使用TraceKit标准化错误字符串。 还有stacktrace.js和其他一些项目。
浏览器兼容性
window.onerror
在浏览器中已经使用了一段时间-您将在IE6和Firefox 2之前的浏览器中找到它。
问题在于,每个浏览器都以不同的方式实现window.onerror
,特别是在向onerror侦听器发送了多少参数以及这些参数的结构方面。
下表是在大多数浏览器中将参数传递给onerror的表:
浏览器 | 信息 | 网址 | 行号 | 颜色 | errorObj |
---|---|---|---|---|---|
火狐浏览器 | ✓ | ✓ | ✓ | ✓ | ✓ |
铬 | ✓ | ✓ | ✓ | ✓ | ✓ |
边缘 | ✓ | ✓ | ✓ | ✓ | ✓ |
IE 11 | ✓ | ✓ | ✓ | ✓ | ✓ |
IE 10 | ✓ | ✓ | ✓ | ✓ | |
IE 9、8 | ✓ | ✓ | ✓ | ||
Safari 10及更高版本 | ✓ | ✓ | ✓ | ✓ | ✓ |
Safari 9 | ✓ | ✓ | ✓ | ✓ | |
Android浏览器4.4 | ✓ | ✓ | ✓ | ✓ |
Internet Explorer 8、9和10对onerror的支持有限可能并不奇怪。 但是您可能会惊讶于Safari仅在Safari 10(2016年发布)中添加了对错误对象的支持。 此外,仍在使用旧版Android浏览器(现在已由Chrome Mobile替换)的较旧的移动电话仍在使用中,并且不会传递错误对象。
没有错误对象,就没有堆栈跟踪属性。 这意味着这些浏览器无法从onerror捕获的错误中检索有价值的堆栈信息。
使用try / catch填充window.onerror
但是有一个解决方法-您可以在try / catch中将代码包装在应用程序中,然后自己捕获错误。 这个错误对象将在每个现代浏览器中包含我们梦stack
以求的stack
属性。
考虑以下帮助程序方法invoke
,该方法在带有参数数组的对象上调用函数:
function invoke(obj, method, args) {
return obj[method].apply(this, args);
}
invoke(Math, 'max', [1, 2]); // returns 2
这里再次invoke
,这次包裹在try / catch中,以捕获所有抛出的错误:
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
当然,在任何地方手动执行此操作都非常麻烦。 您可以通过创建通用包装器实用程序功能来简化此操作:
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
因为JavaScript是单线程的,所以您不需要在所有地方都使用wrap,而只需在每个新堆栈的开始处使用。
这意味着您需要包装函数声明:
- 在您的应用程序开始时(例如,在
$(document).ready
如果您使用jQuery) - 在事件处理程序中(例如
addEventListener
或$.fn.click
) - 基于计时器的回调(例如
setTimeout
或requestAnimationFrame
)
例如:
$(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
});
}));
如果这看起来像是大量工作,那么请放心! 大多数错误报告库都具有用于增强诸如addEventListener
和setTimeout
类的内置函数的机制,因此您不必每次都调用包装工具。 而且,是的,raven-js也这样做。
将错误传输到服务器
好的,您已经完成了自己的工作—已插入window.onerror
,并且还将函数包装在try / catch中,以便捕获尽可能多的错误信息。
最后只有一步:将错误信息传输到服务器。 为了使它起作用,您需要设置某种报告Web服务,该服务将通过HTTP接受您的错误数据,将其记录到文件中和/或将其存储在数据库中。
如果此Web服务与Web应用程序位于同一域中,则只需使用XMLHttpRequest。 在下面的示例中,我们使用jQuery的AJAX函数将数据传输到我们的服务器:
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
});
}
请注意,如果您必须跨不同的源传输错误,则报告端点将需要支持跨源资源共享(CORS)。
摘要
如果到目前为止,您已经拥有了滚动自己的基本错误报告库并将其与应用程序集成所需的所有工具:
-
window.onerror
工作方式以及支持的浏览器 - 如何使用try / catch捕获缺少
window.onerror
堆栈跟踪 - 将错误数据传输到服务器
当然,如果您不想麻烦所有这些,那么有很多商业和开源工具可以为您完成所有繁重的客户端报告。 (请注意:您可能想尝试Sentry 调试JavaScript 。)
而已! 快乐的错误监控 。
From: https://www.sitepoint.com/capture-and-report-javascript-errors-with-window-onerror/