使用window.onerror监视JavaScript错误

我们今天发布的翻译材料专用于使用window.onerror处理JS错误。 这是一个特殊的浏览器事件,当发生未捕获的错误时将触发该事件。 在这里,我们将讨论如何使用onerror事件onerror捕获错误,以及如何将有关错误的信息发送到网站开发者的服务器。 此处理程序可用作您自己的系统的基础,该系统用于收集和分析错误信息。 此外,它是面向错误的库(例如raven-js)中使用的最重要的机制之一。

图片

监听window.onerror事件


您可以通过为window.onerror分配window.onerror充当错误处理程序window.onerror函数来监听onerror事件:

 window.onerror = function(msg, url, lineNo, columnNo, error) { // ...   ... return false; } 

发生错误时将调用此函数;以下参数将传递给该函数:

  • msg错误消息。 例如, Uncaught ReferenceError: foo is not defined
  • url发生错误的脚本或文档的地址。 例如/dist/app.js
  • lineNo发生错误的行号(如果支持)。
  • columnNo行的列号(如果支持)。
  • error对象(如果支持)。

前四个参数告诉开发人员哪个脚本,哪个行以及该行的哪一列发生了错误。 最后一个参数(类型为Error的对象)可能是所有参数中最重要的。 让我们来谈谈。

错误对象和Error.prototype.stack属性


乍一看, Error对象没什么特别的。 它包含三个非常标准的属性lineNumberfileNamelineNumber 。 给定传递给window.onerror事件处理程序的信息,此数据可以被认为是冗余的。

在这种情况下,实际值是非标准属性Error.prototype.stack 。 此属性提供对调用堆栈(错误堆栈)的访问权限,使您可以找出错误发生时程序中正在发生的情况,该函数调用是在错误出现之前发生的。 调用堆栈跟踪可能是调试过程的关键部分。 而且,尽管stack属性不是标准属性,但在所有现代浏览器中都可用。

这就是错误对象的stack属性在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)" 

在我们面前的是未格式化的字符串。 当此属性的内容以这种形式显示时,使用它很不方便。 这是格式化后的外观。

 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属性非常重要。

但是,这里的一切进展都不顺利。 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关键字的实例,并提供有关其他事件(尤其是有关函数调用_evaluateOn_evaluateAndWrap )的详细信息。 同时,在这里,我们仅比较了IE和Chrome提供的功能。 其他浏览器使用自己的方法来显示有关堆栈的数据并选择此数据中包含的信息。

为了使所有这些看起来统一,可以使用第三方工具。 例如,raven-js为此使用了TraceKit。 Stacktrace.js和其他一些项目实现了相同的目的。

各种浏览器支持window.onerror的功能


windows.onerror事件在浏览器中已经存在了一段时间。 特别是,它可以在IE6和Firefox 2中找到。这里的问题是所有浏览器都以不同的方式实现windows.onerror 。 例如,这涉及传递给此事件的处理程序的参数的数量和结构。

下表onerror主要浏览器中传递给onerror处理程序onerror参数。
浏览器
讯息
网址
行号
颜色
errorObj
火狐浏览器





镀铬





边缘





IE 11





IE10




没有啦
IE 9.8



没有啦
没有啦
Safari 10及更高版本





Safari 9




没有啦
Android浏览器4.4




没有啦

也许不足为奇,Internet Explorer 8、9和10对onerror支持有限。 但是,在Safari浏览器中对错误对象的支持仅出现在2016年发布的第10版中似乎并不寻常。 此外,还有一些使用标准Android浏览器的旧式移动设备,该浏览器也不支持错误对象。 在现代版本的Android中,此浏览器已由Chrome Mobile取代。

如果没有错误对象可供使用,那么就没有关于堆栈跟踪的数据。 这意味着不支持错误对象的浏览器不会在使用onerror处理程序的标准脚本中提供堆栈信息。 正如我们所说的,这一点非常重要。

使用try / catch构造针对window.onerror的Polyfill开发


为了在不支持将onerror对象传递给onerror处理程序的浏览器中获取有关堆栈的信息,可以使用以下技巧。 您可以将代码包装在try/catch构造中,然后自己捕获错误。 在所有现代浏览器中,产生的错误对象将包含我们需要的stack属性。
看一下invoke()辅助方法代码,该代码调用对象的给定方法,并向其传递参数数组。

 function invoke(obj, method, args) { return obj[method].apply(this,args); } 

这是使用方法。

 invoke(Math, 'max', [1,2]); //  2 

这是相同的invoke() ,但是现在方法调用包装在try/catch ,它使您可以捕获可能的错误。

 function invoke(obj, method, args) { try {   return obj[method].apply(this,args); } catch(e) {   captureError(e);//      throw e;//      } } invoke(Math,'highest',[1,2]); //  ,     Math.highest 

当然,将此类结构手动添加到可能需要它们的所有位置非常昂贵。 通过创建通用帮助函数可以简化此任务。

 function wrapErrors(fn) { //      if(!fn.__wrapped__) {   fn.__wrapped__ = function() {     try{       return fn.apply(this,arguments);     }catch(e){       captureError(e);//          throw e;//          }   }; } return fn.__wrapped__; } var invoke = wrapErrors(function(obj, method, args) { returnobj[method].apply(this,args); }); invoke(Math,'highest',[1,2]);//,   Math.highest 

由于JavaScript使用单线程代码执行模型,因此该包装器仅应与新堆栈开始时的那些函数调用一起使用。 无需将所有函数调用包装在其中。

结果,该功能需要在以下位置使用:

  • 应用程序的启动位置(例如,使用jQuery时,在$(document).ready函数中)
  • 在事件处理程序中(例如,在addEventListener$.fn.click格式的$.fn.click
  • 在计时器事件调用的回调中(例如,它是setTimeoutrequestAnimationFrame

这是使用wrapErrors函数的示例。

 $(wrapErrors(function () {//    doSynchronousStuff1();//     setTimeout(wrapErrors(function () {   doSynchronousStuff2();//      })); $('.foo').click(wrapErrors(function () {   doSynchronousStuff3();//     })); })); 

可以自己将此类构造添加到代码中,但这是一项非常耗时的任务。 在这种情况下,作为一种方便的替代方法,可以考虑使用用于处理错误的库,例如,该库具有addEventListenersetTimeout addEventListener错误的工具的机制。

错误转移到服务器


因此,现在我们可以使用windows.onerror或基于try/catch辅助功能来拦截错误信息。 这些错误发生在客户端,在被拦截后,我们希望对其进行处理并采取措施消除它们。 为此,需要将它们转移到我们的服务器。 为此,您需要准备一个Web服务,该服务将通过HTTP接收有关错误的信息,然后以某种方式保存它们以进行进一步处理,例如-将其写入日志文件或数据库。

如果此Web服务与Web应用程序位于同一域中,则XMLHttpRequest就足够了。 下面的示例演示如何使用函数从jQuery执行AJAX查询,以将数据传输到服务器。

 function captureError(ex){ var errorData = {   name:ex.name,// : ReferenceError   message:ex.line,// : x is undefined   url:document.location.href,   stack:ex.stack//   ; ,     ! }; $.post('/logger/js/',{   data:errorData }); } 

请记住,如果您需要发送跨域请求以将有关错误的信息发送到服务器,则必须注意支持此类请求。

总结


您已经了解了创建服务以捕获错误并将错误信息发送到服务器的基础知识。 特别是在这里,我们研究了以下问题:

  • onerror事件的功能及其在各种浏览器中的支持。
  • onerror不支持使用错误对象的情况下,使用try/catch机制获取有关调用堆栈的信息。
  • 将错误数据传输到开发人员的服务器。

在了解了上述机制的工作原理之后,您已经获得了基础知识,可以使您开始创建自己的用于处理错误的系统,并在工作中阐明其他细节。 也许这种情况对于涉及某些应用程序的情况尤其重要,例如出于安全原因,不打算使用第三方库。 如果您的应用程序允许使用第三方代码,则可以很好地找到监视JS错误的正确工具。 这些工具包括SentryRollbarTrackJS和其他类似项目。

亲爱的读者们! 您使用哪些监视JS错误的工具?

Source: https://habr.com/ru/post/zh-CN413173/


All Articles