美文网首页前端技术
前端异常监控window.onerror unhandledre

前端异常监控window.onerror unhandledre

作者: pansly | 来源:发表于2019-03-29 09:34 被阅读0次

    关于前端异常监控,我们需要做到捕获JS异常和代码中未捕获的promise异常,然后向服务器上报

    捕获JS异常

    在了解捕获JS异常前先了解下window.onerror语法

    window.onerror = function(message, source, lineno, colno, error) { ... }
    
    

    函数参数:

    • message:错误信息(字符串)。可用于HTML onerror=""处理程序中的event
    • source:发生错误的脚本URL(字符串)
    • lineno:发生错误的行号(数字)
    • colno:发生错误的列号(数字)
    • errorError对象(对象)

    若该函数返回true,则阻止执行默认事件处理函数。

    window.addEventListener('error')

    window.addEventListener('error', function(event) { ... })
    

    ErrorEvent 类型的event包含有关事件和错误的所有信息。

    element.onerror

    element.onerror = function(event) { ... }
    

    element.onerror使用单一Event参数的函数作为其处理函数。

    我们可以看到函数正常是可以收集到错误字符串信息、发生错误的js文件,错误所在的行数、列数、和Error对象(里面会有调用堆栈信息等),把这些信息回传到server端即可,再配合sourcemap的话我们就可以知道是源码中的哪一行出错了,从而实现完美的错误实时监控系统了。然而要完美还是需要做很多工作的。

    首先,我们的js文件一般都是和网站不同域的,这是为了提高页面的渲染速度以及架构的可维护性(单独CDN域名,充分利用浏览器http并发数)。这样的js文件中发生错误我们直接监控你会发现你啥信息都收集不到。

    实验一:我们的站点是a.com,页面中引用了两个js文件,一个是a.com域名下的a.js,一个是b.com域名下的b.js,我们在a.js文件中添加window.onerror监控,在b.js文件中主动抛出错误

    <!-- index.html  -->
    <script type="text/javascript" src="http://a.com/a.js" ></script>
    <script type="text/javascript" src="http://b.com/b.js" ></script>
    
    
    // a.js
    window.onerror = function (message, url, line, column, error) {
      console.log('log---onerror::::',message, url, line, column, error);
    }
    
    
    // b.js
    throw new Error('this is the error happened in b.js');
    
    

    我们可以看到下图的结果,onerror函数拿到的信息是Script error, a 0 null,啥卵用都没有,你完全不知道发生了什么错误,哪个文件发生的错误。

    这是浏览器的同源策略,当加载自不同域(协议、域名、端口三者任一不同)的脚本中发生语法(?)错误时,为避免信息泄露,语法错误的细节将不会报告,而代之简单的"Script error."

    image

    但是我们确实是需要知道发生错误的具体信息啊,不然监控就没有意义了。既然又是类同源限制的问题,那肯定是可以通过CORS来解决了。

    实验二:我们给b.js加上Access-Control-Allow-Origin:*的response header,后面我们会发现还是没啥变化。

    image

    实验三:我们继续给b.js加上crossorigin属性,发现可以了,想要的信息都收集到了,nice

    <!-- index.html  -->
    <script type="text/javascript" src="http://a.com/a.js" ></script>
    <script type="text/javascript" src="http://b.com/b.js"  crossorigin></script>
    
    
    image

    结论:如果想通过onerror函数收集不同域的js错误,我们需要做两件事:

    1. 相关的js文件上加上Access-Control-Allow-Origin:*的response header
    2. 引用相关的js文件时加上crossorigin属性

    注意: 以上两步缺一不可。实验二告诉我们,如果只是加上Access-Control-Allow-Origin:*的话,错误还是无法捕获。如果只加上crossorigin属性,浏览器会报无法加载的错误,如下图

    image

    可是。。。
    如果你使用sentry的raven.js的话,你会发现你什么都不用做,他依然可以帮你捕获到一些错误的非常具体信息,确实是有点神奇啊,具体怎么做的?关键就是raven源码中的install方法中调用的_instrumentTryCatch函数起了作用,他通过tryCatch的方式wrap了一些关键函数,使得这些函数里的报错能够捕获,_instrumentTryCatch的具体实现原理我们后面再说

        install: function() {
            var self = this;
    
            if (self.isSetup() && !self._isRavenInstalled) {
                TraceKit.report.subscribe(function () {
                    self._handleOnErrorStackInfo.apply(self, arguments);
                });
                if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) {
                  self._instrumentTryCatch();// 通过tryCatch来wrap关键函数,从而获得error的具体信息
                }
    
                if (self._globalOptions.autoBreadcrumbs)
                    self._instrumentBreadcrumbs();
    
                // Install all of the plugins
                self._drainPlugins();
    
                self._isRavenInstalled = true;
            }
    
            Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
            return this;
        },
    
    

    其实如果你真的什么都不做,raven也只是能捕获一些异步错误,同步错误还是无法捕获,所以你即使使用了sentry等第三方的错误收集库,你还是需要加上Access-Control-Allow-Origin:*和crossorigin属性

    image

    捕获未处理的promise异常

    为了保证可读性,本文采用意译而非直译,并且对源代码进行了大量修改。另外,本文版权归原作者所有,翻译仅用于学习。

    使用Promise编写异步代码时,使用reject来处理错误。有时,开发者通常会忽略这一点,导致一些错误没有得到处理。例如:

    function main() {
        asyncFunc()
        .then(···)
        .then(() => console.log('Done!'));
    }
    

    这篇博客将分别介绍在浏览器与Node.js中,如何捕获那些未处理的Promise错误。由于没有使用catch方法捕获错误,当asyncFunc()函数reject时,抛出的错误则没有被处理。

    浏览器中未处理的Promise错误

    一些浏览器(例如Chrome)能够捕获未处理的Promise错误。

    unhandledrejection

    监听unhandledrejection事件,即可捕获到未处理的Promise错误:

    window.addEventListener('unhandledrejection', event => ···);
    

    promise: reject的Promise这个事件是PromiseRejectionEvent实例,它有2个最重要的属性:

    • reason: Promise的reject值

    示例代码:

    window.addEventListener('unhandledrejection', event => {
        console.log(event.reason); 
    });
    function foo() {
        Promise.reject('Hello, Fundebug!');
    }
    foo();
    

    当一个Promise错误最初未被处理,但是稍后又得到了处理,则会触发rejectionhandled事件:

     window.addEventListener('rejectionhandled', event => ···);
    

    示例代码:这个事件是PromiseRejectionEvent实例。

    window.addEventListener('unhandledrejection', event =>
    {
        console.log(event.reason); // 打印"Unhandle Promise Error!"
    });
     
    window.addEventListener('rejectionhandled', event => {
        console.log('rejection handled'); // 1秒后打印"rejection handled"
    });
     
    function foo() {
        return Promise.reject('Unhandle Promise Error!');
    }
     
    var r = foo();
     
    setTimeout(() => {
        r.catch(e =>{});
    }, 1000);
    

    参考文献:
    GlobalEventHandlers.onerror
    What the heck is "Script error"?

    相关文章

      网友评论

        本文标题:前端异常监控window.onerror unhandledre

        本文链接:https://www.haomeiwen.com/subject/lrrvvqtx.html