美文网首页
js错误监控总结

js错误监控总结

作者: Small_Song | 来源:发表于2020-10-30 13:59 被阅读0次

    前言

    做好错误监控,将用户使用时的错误日志上报,可以帮助我们更快的解决一些问题。目前开源的比较好的前端监控有

    那前端监控是怎么实现的呢?要想了解这个,需要知道前端错误大概分为哪些以及如何捕获处理。

    前端错误分为JS运行时错误、资源加载错误和接口错误三种。

    一、JS运行时错误

    JS运行时错误一般使用window.onerror捕获,但是有一种特殊情况就是promise被reject并且错误信息没有被处理的时候抛出的错误

    1.1 一般情况的JS运行时错误

    使用window.onerror和window.addEventListener('error')捕获。

    window.onerror = function (msg, url, lineNo, columnNo, error) 
        { 
           // 处理error信息
        } 
    
        window.addEventListener('error', event =>  
        {  
           console.log('addEventListener error:' + event.target); 
        }, true); 
        // true代表在捕获阶段调用,false代表在冒泡阶段捕获。使用true或false都可以
    

    例子:https://jsbin.com/lujahin/edit?html,console,output 点击button抛出错误,分别被window.onerror和window.addEventListener('error')捕获

    1.2 Uncaught (in promise)

    当promise被reject并且错误信息没有被处理的时候,会抛出一个unhandledrejection,并且这个错误不会被window.onerror以及window.addEventListener('error')捕获,需要用专门的window.addEventListener('unhandledrejection')捕获处理

    window.addEventListener('unhandledrejection', event => 
        { 
           console.log('unhandledrejection:' + event.reason); // 捕获后自定义处理
        });
    

    https://developer.mozilla.org...
    例子:https://jsbin.com/jofomob/edit?html,console,output 点击button抛出unhandledrejection错误,并且该错误仅能被window.addEventListener('unhandledrejection')捕获

    1.3 console.error

    一些特殊情况下,还需要捕获处理console.error,捕获方式就是重写window.console.error

    var consoleError = window.console.error; 
    window.console.error = function () { 
        alert(JSON.stringify(arguments)); // 自定义处理
        consoleError && consoleError.apply(window, arguments); 
    };
    

    例子:https://jsbin.com/pemigew/edit?html,console,output

    1.4 特别说明跨域日志

    什么是跨域脚本error?

    https://developer.mozilla.org...
    当加载自不同域的脚本中发生语法错误时,为避免信息泄露(参见bug 363897),语法错误的细节将不会报告,而代之简单的"Script error."。在某些浏览器中,通过在<script>使用crossorigin属性并要求服务器发送适当的 CORS HTTP 响应头,该行为可被覆盖。一个变通方案是单独处理"Script error.",告知错误详情仅能通过浏览器控制台查看,无法通过JavaScript访问。

    window.onerror = function (msg, url, lineNo, columnNo, error) {
        var string = msg.toLowerCase();
        var substring = "script error";
        if (string.indexOf(substring) > -1){
            alert('Script Error: See Browser Console for Detail');
        } else {
            var message = [
                'Message: ' + msg,
                'URL: ' + url,
                'Line: ' + lineNo,
                'Column: ' + columnNo,
                'Error object: ' + JSON.stringify(error)
            ].join(' - ');
    
            alert(message);
        }
    
        return false;
    };
    

    例子: http://sandbox.runjs.cn/show/... 请打开页面打开控制台。该页面分别加载了两个不同域的js脚本,配置了crossorigin的window.onerror可以报出详细的错误,没有配置crossorigin只能报出'script error',并且没有错误信息

    为了跨域捕获JavaScript异常,可执行以下两个解法:

    解法一、1.添加crossorigin="anonymous"属性。

    <script src="http://another-domain.com/app.js" crossorigin="anonymous"></script>
    

    此步骤的作用是告知浏览器以匿名方式获取目标脚本。这意味着请求脚本时不会向服务端发送潜在的用户身份信息(例如Cookies、HTTP证书等)。

    2.添加跨域HTTP响应头。

    Access-Control-Allow-Origin: * 或者 Access-Control-Allow-Origin: http://test.com

    解法二 、try catch

    <!doctype html>
    <html>
    <head>
      <title>Test page in http://test.com</title>
    </head>
    <body>
      <script src="http://another-domain.com/app.js"></script>
     <script>
      window.onerror = function (message, url, line, column, error) {
        console.log(message, url, line, column, error);
      }
      try {
        foo(); // 调用app.js中定义的foo方法
      } catch (e) {
        console.log(e);
        throw e;
      }
      </script>
    </body>
    </html>
    

    再次运行,输出结果如下:

    => ReferenceError: bar is not defined
         at foo (http://another-domain.com/app.js:2:3)
         at http://test.com/:15:3
    => "Script error.", "", 0, 0, undefined
    

    可见try catch中的Console语句输出了完整的信息,但window.onerror中只能捕获“Script error”。根据这个特点,可以在catch语句中手动上报捕获的异常,详情请参见API 使用指南

    1.5 特别说明sourceMap

    在线上由于JS一般都是被压缩或者打包(webpack)过,打包后的文件只有一行,因此报错会出现第一行第5000列出现JS错误,给排查带来困难。sourceMap存储打包前的JS文件和打包后的JS文件之间一个映射关系,可以根据打包后的位置快速解析出对应源文件的位置。

    但是出于安全性考虑,线上设置sourceMap会存在不安全的问题,因为网站使用者可以轻易的看到网站源码,此时可以设置.map文件只能通过公司内网访问降低隐患

    sourceMap配置devtool: 'inline-source-map'
    如果使用了uglifyjs-webpack-plugin 必须把 sourceMap设置为true
    https://doc.webpack-china.org...

    1.6 其它

    1.6.1 sentry把所有的回调函数使用try catch封装一层
    https://github.com/getsentry/raven-js/blob/master/src/raven.js

    1.6.2 vue errorHandler
    https://vuejs.org/v2/api/#errorHandler
    其原理也是使用try catch封装了nextTick,$emit, watch,data等
    https://github.com/vuejs/vue/blob/dev/dist/vue.runtime.js

    二、资源加载错误

    使用window.addEventListener('error')捕获,window.onerror捕获不到资源加载错误

    https://jsbin.com/rigasek/edit?html,console 图片资源加载错误。此时只有window.addEventListener('error')可以捕获到

    window.onerror和window.addEventListener('error')的异同:相同点是都可以捕获到window上的js运行时错误。区别是1.捕获到的错误参数不同 2.window.addEventListener('error')可以捕获资源加载错误,但是window.onerror不能捕获到资源加载错误

    window.addEventListener('error')捕获到的错误,可以通过target?.src || target?.href区分是资源加载错误还是js运行时错误

    三、接口错误

    所有http请求都是基于xmlHttpRequest或者fetch封装的。所以要捕获全局的接口错误,方法就是封装xmlHttpRequest或者fetch

    3.1 封装xmlHttpRequest

    if(!window.XMLHttpRequest) return;
    var xmlhttp = window.XMLHttpRequest;
    var _oldSend = xmlhttp.prototype.send;
    var _handleEvent = function (event) {
        if (event && event.currentTarget && event.currentTarget.status !== 200) {
              // 自定义错误上报 }
    }
    xmlhttp.prototype.send = function () {
        if (this['addEventListener']) {
            this['addEventListener']('error', _handleEvent);
            this['addEventListener']('load', _handleEvent);
            this['addEventListener']('abort', _handleEvent);
        } else {
            var _oldStateChange = this['onreadystatechange'];
            this['onreadystatechange'] = function (event) {
                if (this.readyState === 4) {
                    _handleEvent(event);
                }
                _oldStateChange && _oldStateChange.apply(this, arguments);
            };
        }
        return _oldSend.apply(this, arguments);
    }
    

    3.2 封装fetch

    if(!window.fetch) return;
        let _oldFetch = window.fetch;
        window.fetch = function () {
            return _oldFetch.apply(this, arguments)
            .then(res => {
                if (!res.ok) { // True if status is HTTP 2xx
                    // 上报错误
                }
                return res;
            })
            .catch(error => {
                // 上报错误
                throw error;  
            })
    }
    

    结论

    1. 使用window.onerror捕获JS运行时错误
    2. 使用window.addEventListener('unhandledrejection')捕获未处理的promise reject错误
    3. 重写console.error捕获console.error错误
    4. 在跨域脚本上配置crossorigin="anonymous"捕获跨域脚本错误
    5. window.addEventListener('error')捕获资源加载错误。因为它也能捕获js运行时错误,为避免重复上报js运行时错误,此时只有event.srcElement inatanceof HTMLScriptElement或HTMLLinkElement或HTMLImageElement时才上报
    6. 重写window.XMLHttpRequest和window.fetch捕获请求错误

    利用以上原理,简单写了一个JS监控,只处理了一些JS错误,暂时没有做和性能相关的监控
    https://github.com/Lie8466/better-js

    如果发现文章有错误,欢迎指正。

    相关文章

      网友评论

          本文标题:js错误监控总结

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