使用window.onerror捕获和报告JavaScript错误

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.

onerror是一个特殊的浏览器事件,每当引发未捕获的JavaScript错误时都会触发。 这是记录客户端错误并将其报告给服务器的最简单方法之一。 它也是Sentry的客户端JavaScript集成 (raven-js)起作用的主要机制之一。

You listen to the onerror event by assigning a function to window.onerror:

通过将函数分配给window.onerror来监听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"

    msg –与错误关联的消息,例如“ Uncaught ReferenceError:未定义foo”
  • url – The URL of the script or document associated with the error, e.g. "/dist/app.js"

    url –与错误关联的脚本或文档的URL,例如“ /dist/app.js”
  • lineNo – The line number (if available)

    lineNo –行号(如果有)
  • columnNo – The column number (if available)

    columnNo –列号(如果有)
  • error – The Error object associated with this error (if available)

    错误 –与该错误关联的Error对象(如果有)

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.

前四个参数告诉您发生错误的脚本,行和列。 最后一个参数Error对象也许是最有价值的。 让我们学习为什么。

Error对象和error.stack ( 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.

乍一看,Error对象不是很特殊。 它包含3个标准化的属性: messagefileNamelineNumber 。 已经通过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 error stack trace can be a critical part of debugging. And despite being non-standard, this property is available in every modern browser.

有价值的部分是非标准属性:Error.prototype.stack。 此堆栈属性告诉您错误发生时程序的每个帧位于哪个源位置。 错误堆栈跟踪可能是调试的关键部分。 尽管不是标准的,但此属性在每种现代浏览器中都可用。

Here's an example of the Error object's stack property in Chrome 46:

这是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)"

Hard to read, right? The stack property is actually just an unformatted string.

很难读,对不对? stack属性实际上只是一个未格式化的字符串。

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:

只有一个障碍: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)

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.

不仅每个帧的格式不同,而且帧的细节也更少。 例如,Chrome可以识别出已使用了new关键字,并且可以更深入地了解eval调用。 这只是IE 11与Chrome的对比-其他类似的浏览器具有不同的格式和细节。

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.

幸运的是,那里有一些工具可以标准化堆栈属性,以便在浏览器之间保持一致。 例如,raven-js使用TraceKit标准化错误字符串。 还有stacktrace.js和其他一些项目。

浏览器兼容性 ( 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.

window.onerror在浏览器中已有一段时间了,您会在IE6和Firefox 2之前的浏览器中找到它。

The problem is that every browser implements window.onerror differently, particularly, in how many arguments are sent to the onerror listener and the structure of those arguments.

问题是每个浏览器实现window.onerror的方式都不同,特别是在向onerror侦听器发送了多少参数以及这些参数的结构方面。

Here's a table of which arguments are passed to onerror in most browsers:

下表是在大多数浏览器中将参数传递给onerror的表:

BrowserMessageURLlineNocolNoerrorObj
Firefox
Chrome
Edge
IE 11
IE 10
IE 9, 8
Safari 10 and up
Safari 9
Android Browser 4.4
浏览器 信息 网址 行号 颜色 errorObj
火狐浏览器
Chrome
边缘
IE 11
IE 10
IE 9、8
Safari 10及更高版本
Safari 9
Android浏览器4.4

It's probably not a surprise that Internet Explorer 8, 9, and 10 have limited support for onerror. But you might be surprised that Safari only added support for the error object in in Safari 10 (released in 2016). Additionally, older mobile handsets that still use the stock Android browser (now replaced with Chrome Mobile), are still out there and do not pass the error object.

Internet Explorer 8、9和10对onerror的支持有限可能并不奇怪。 但是您可能会惊讶于Safari仅在Safari 10(2016年发布)中添加了对错误对象的支持。 此外,仍在使用旧版Android浏览器(现在已由Chrome Mobile替换)的较旧的移动电话仍在使用中,并且不会传递错误对象。

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.

没有错误对象,就没有堆栈跟踪属性。 这意味着这些浏览器无法从onerror捕获的错误中检索有价值的堆栈信息。

使用try / catch进行polyfilling window.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.

但是有一个解决方法-您可以在try / catch中将代码包装在应用程序中,然后自己捕获错误。 该错误对象将在每个现代浏览器中包含我们梦stack以求的堆栈属性。

Consider the following helper method, invoke, which calls a function on an object with an array of arguments:

考虑以下帮助程序方法invoke,该方法在带有参数数组的对象上调用函数:

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:

这里再次调用,这次包裹在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

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
        throwe;// re-throw the error
      }
    };
  }

  return fn.**wrapped**;
}

var invoke = wrapErrors(function(obj, method, args) {
  returnobj[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.

因为JavaScript是单线程的,所以您不需要在所有地方都使用wrap,而只需在每个新堆栈的开始处使用。

That means you'll need to wrap function declarations:

这意味着您需要包装函数声明:

  • At the start of your application (e.g., in $(document).ready if you use jQuery)

    在您的应用程序开始时(例如,在$ {document).ready中,如果您使用jQuery)
  • In event handlers (e.g., addEventListener or $.fn.click)

    在事件处理程序中(例如,addEventListener或$ .fn.click)
  • Timer-based callbacks (e.g., setTimeout or requestAnimationFrame)

    基于计时器的回调(例如setTimeout或requestAnimationFrame)

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.

如果这看起来像很多工作的麻烦,那就不用担心! 大多数错误报告库都具有用于增强诸如addEventListener和setTimeout之类的内置函数的机制,因此您不必每次都调用包装工具。 而且,是的,raven-js也这样做。

将错误传输到您的服务器 ( 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.

好的,您已经完成了自己的工作-已插入window.onerror,并且还将函数包装在try / catch中,以便捕获尽可能多的错误信息。

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.

最后只有一步:将错误信息传输到服务器。 为了使其正常工作,您需要设置某种报告Web服务,该服务将通过HTTP接受您的错误数据,将其记录到文件中和/或将其存储在数据库中。

If this web service is on the same domain as your web application, just use XMLHttpRequest. In the example below, we use jQuery's AJAX function to transmit the data to our servers:

如果此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
  });

}

Note that, if you have to transmit your error across different origins, your reporting endpoint will need to support Cross Origin Resource Sharing (CORS).

请注意,如果您必须跨不同的源传输错误,则报告端点将需要支持跨源资源共享(CORS)。

摘要 ( 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 onerror works, and what browsers it supports

    onerror的工作原理以及支持的浏览器
  • How to use try/catch to capture stack traces where onerror is lacking

    如何使用try / catch捕获缺少onerror的堆栈跟踪
  • 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 to debug JavaScript.)

当然,如果您不想麻烦所有这些,那么有很多商业和开源工具可以为您完成所有繁重的客户端报告。 (请注意:您可能想尝试Sentry 调试JavaScript 。)

That's it! Happy error monitoring.

而已! 快乐的错误监控

翻译自: https://scotch.io/tutorials/capture-and-report-javascript-errors-with-windowonerror

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值