JS错误监控总结

前言

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

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

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

1、JS运行时错误

JS运行时错误通常使用window.onerror捕获,可是有一种特殊状况就是promise被reject而且错误信息没有被处理的时候抛出的错误webpack

1.1 通常状况的JS运行时错误

使用window.onerror和window.addEventListener('error')捕获。其中window.onerror含有详细的error信息(error.stack),并且兼容性更好,因此通常JS运行时错误使用window.onerror捕获处理git

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')捕获处理github

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.errorweb

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?api

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

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

1.5 特别说明sourceMap

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

可是出于安全性考虑,线上设置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

2、资源加载错误

使用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不能捕获到资源加载错误

3、接口错误

全部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

若是发现文章有错误,欢迎指正。

相关文章
相关标签/搜索