最近在写谬误上报,记录一下,如果对你有所帮忙,荣幸之至;
第一次写,有点啰嗦,见谅!
大略分为三个局部:

  1. 谬误收集
  2. 谬误筛选
  3. 谬误上报
  4. 注意事项
  5. 残缺示例

一、谬误收集
js的谬误个别分为:运行时谬误、资源加载谬误、网络申请谬误;
对于语法错误、资源加载谬误,供咱们抉择的谬误收集形式个别是:

window.addEventListener('error', e => {}, true);window.onerror = function (msg, url, line, col, error) {}

**划重点:**

  • 两者取得的参数不一样;
  • window.addEventListener能监测到资源(css,img,script)加载失败;
  • window.addEventListener能捕捉到window.onerror能捕捉到的谬误;
  • 二者都不能捕捉到console.error的错误信息;
  • 二者都不能捕捉到:当promise被reject并且错误信息没有被解决时的错误信息;

因而咱们能够这样收集谬误:

 window.addEventListener("error", e => {        if (!e) {            return;        }        console.log(e.message);//错误信息        conosle.log(e.filename);//产生谬误的文件名        console.log(e.lineno);//产生谬误的行号(代码压缩合并对值有影响)        console.log(e.colno);//产生谬误的列号(代码压缩合并对值有影响)        const _target = e.target || e.srcElement;        if (!_target) {            return;        }        if (_target === window) {            //语法错误            let _error = e.error;            if (_error) {                console.log(_error.stack);//谬误的堆栈信息            }        } else {            // 元素谬误,比方援用资源报错            let _src = _target.src;            console.log(_src);//_src: 谬误的资源门路        }    }, true);

当是运行时的语法错误时,咱们能够拿到报错的行号,列号,错误信息,谬误堆栈,以及产生谬误的脚本的门路及名字。
当是资源门路谬误时,咱们能够拿到谬误资源的门路及名字。

至此,咱们就拿到了想要的资源加载谬误、运行时语法错误的信息,那ajax网络申请谬误怎么办呢?

此时:有两个形式能够抉择

throw new Error('抛出的一个谬误');console.error('打印一个谬误');//上面会讲

咱们后面定义的办法能够收集到throw new Error抛出的谬误,然而要留神,抛出谬误同样也会阻断后续的程序,应用的时候要小心;如果你的我的项目中也封装了http申请的话,可参照上面代码:

    //基于jquery    function ajaxFun (params) {        var _d = {            type: params.type || 'POST',            url: params.url || '',            data: params.data || null,            dataType: params.dataType || 'JSON',            contentType: params.contentType || 'application/x-www-form-urlencoded',            beforeSend: function (request) {                           },            success: function (data, status, xhr) {            },            error: function (xhr, type, error) {                throw new Error(params.url + '申请失败');            }        }        $.ajax(_d);    }

下面的代码是用jquery封装的申请,我在error办法外面抛出了这个ajax申请的谬误,因为抛出谬误前面没有其余业务逻辑,不会有什么问题,这里我只要求收集ajax的error办法谬误,如果你的我的项目要求解决所有异样谬误,比方token生效导致的登陆失败,就须要在success函数外面也做解决了。然而,要留神throw new Error('抛出的一个谬误')console.error('打印一个谬误')的区别。

当应用console.error打印谬误时,后面的window.addEventListener形式没法收集到,然而咱们能够通过其余形式收集到谬误,上面是一个更非凡的例子;

**特例:**

js使用范畴很广,有些状况,这样是不可能收集到咱们想要的谬误的;

打个比方,咱们用 cocos creator 引擎写游戏时,加载资源是应用引擎的办法,当产生资源不存在的谬误时,咱们是不晓得的,然而,咱们发现 cocos creator 引擎会将谬误打印到控制台,那也是引擎做的操作,咱们一番顺藤摸瓜,会发现,cocos creator 引擎在底层报错都是用cc.error,翻看cc.error的源码,咱们就看见了咱们想看见的货色了console.error(),这样一来,晓得谬误是怎么来的,就好办了。(具体情况,具体看待,这里只是凑巧cocos是这么解决的,其余引擎可能不太一样)

let _windowError = window.console.error;window.console.error = function () {    let _str = JSON.stringify(arguments);    console.log(_str);    _windowError && _windowError.apply(window, arguments);}

复写console.error后,无论和人在何处应用这个函数,咱们都能够保障这个打印被咱们解决过,
记住,肯定要先将原来的console.error接管一下,并且在实现咱们须要的业务后,执行原来console.error,
保障不会影响到其余的逻辑。


二、谬误筛选

兴许你会纳闷?不是所有的谬误都上报么,为什么要筛选呢?
大多数状况,咱们收集到谬误,而后上报即可,
然而,有时候,会有循环报错资源加载失败始终重试,始终失败 等种种非凡状况,如果依照失常的上报流程,那么可能会产生在短短几秒的工夫内,收集到了上千、上万条数据,导致程序卡顿,甚至是解体。

因而,咱们须要对谬误进行筛选。

let _errorMap = {};//用于谬误筛选的对象;let _errorArg = [];//寄存错误信息的数组;

全局保护一个_errorMap,用于谬误筛选的对象,每当有谬误时,咱们依照约定好的规定,组成一个key,和_errorMap曾经存在的key进行比对,如果不存在,证实是新的谬误,须要上报,如果是曾经上报的谬误,就不再解决。
当然,为了避免_errorMap无限大、以及谬误漏报,当_errorMap的key的数量大于肯定数量时,咱们须要将_errorMap的key清空,这时候可能呈现后面曾经上报的谬误再次上报,然而不要紧,这个反复能够承受。

这个临界值能够依据理论状况定,我我的项目中最大值为100。

对于下面这个约定好的规定,其实就是依据咱们下面收集到的无关谬误的信息,组成的一个惟一key值,
能实现唯一性且越短越好即可

//下面的代码,复制下来,不便看window.addEventListener("error", e => {        if (!e) {            return;        }        console.log(e.message);//错误信息        conosle.log(e.filename);//产生谬误的文件名        console.log(e.lineno);//产生谬误的行号(代码压缩合并对值有影响)        console.log(e.colno);//产生谬误的列号(代码压缩合并对值有影响)        const _target = e.target || e.srcElement;        if (!_target) {            return;        }        if (_target === window) {            //语法错误            let _error = e.error;            if (_error) {                console.log(_error.stack);//谬误的堆栈信息            }        } else {            // 元素谬误,比方援用资源报错            let _src = _target.src;            console.log(_src);//_src: 谬误的资源门路        }}, true);
对于语法错误,能够依据报错的文件名,行号,列号,组成keylet _key = `${e.filename}_${e.lineno}_${e.colno}`;对于资源加载谬误,能够依据谬误资源的门路作为key:let _key = e.src;

拿到key之后,咱们就能够存贮谬误了,

上面是存储的残缺代码:

function _sendErr(key, errType, errMsg) {        //筛选        if (_ErrorMap.hasOwnProperty(key)) {            //筛选到雷同的谬误,可将值加一,能够判断谬误呈现的次数            _ErrorMap[key] += 1;            return;        }        //阈值        if (_ErrorArg.length >= 100) {            return;        }        //存储谬误        //对于要发给后端的数据,可依据需要组织,数据结构        _ErrorArg.push({            errType: errType,//谬误类型            errMsg: errMsg || '',//错误信息            ver: _ver || '',//版本号            timestamp: new Date().getTime(),//工夫戳        });        //寄存错误信息的数组的阈值        if (Object.keys(_ErrorMap).length >= 100) {            //达到阈值之后,清空去重对象            _ErrorMap = {};        }        _ErrorMap[key] = 1;    }

存储谬误的数组也须要阈值,理论使用中,咱们能够管制每次上报的谬误条数,然而,肯定得记得曾经上报的谬误肯定要从数组中移出。此外,上报的数据结构依据需要能够调整,个别蕴含错误信息、堆栈信息、加载失败资源的门路。


三、谬误上报

难道不是一收集到谬误就上报?
同时呈现一个两个谬误,当然能够立刻上报,
然而如果千百个谬误在短短的几秒钟呈现,就会呈现网络拥挤,甚至是程序解体。
因而,个别都会全局保护一个计时器,提早上报;

let _ErrorTimer = null;timerError();function timerError() {    clearTimeout(_ErrorTimer);    let _ErrorArg = g.IndexGlobal.ErrorArg;//后面提到的全局谬误存贮数组    let _ErrorArgLength = _ErrorArg.length;    if (_ErrorArgLength > 0) {        let _data = [];//要发送的错误信息,因为是一次性发5条,放零时数组中。        //组织要发送的错误信息        for (let i = 0; i < _ErrorArgLength; i++) {            if (_data.length >= 5) {                break;            }            _data.push(_ErrorArg.shift());        }                if (_data.length) {            //发送错误信息            //jq ajax            g.IndexGlobal.errorSend(_data, function (p) {                //失败                //如果发送失败,将未发送的数据,从新放入存储错误信息的数组中                if (p && p.data && p.data.data) {                    if (_ErrorArg.length >= 100) {                        return;                    }                    let _ag = p.data.data;                    try {                        g.IndexGlobal.ErrorArg.push(...JSON.parse(_ag));                    } catch (error) {                    }                }            });        }    }    //计时器距离,当数组长度大于20时,一秒执行一次,默认2秒一次    let _ti = _ErrorArgLength >= 20 ? 1000 : 2000;    _ErrorTimer = setTimeout(timerError, _ti);}

咱们能够依据谬误的数量,调整谬误上报的频率。然而这个距离个别不要太小,不然容易出问题。


四、注意事项

1.无论是window.addEventLister还是console.error,在咱们定义这些办法之前报的所有谬误,咱们是收集不到的,
怎么解决呢,很简略,js程序执行,咱们能够将相干代码放在最前头,

<!DOCTYPE html><html><script>    //处理错误的代码    window.addEventLister;    console.error = function(){}</script><head>    <meta charset="utf-8">    <link rel="stylesheet" href="">    <link rel="stylesheet" href=""></head><body></body><script src="js/zepto.min.js"></script><script src="js/a.js"></script><script>    //开始谬误上报计数器</script></html>

然而,要留神,放在最后面的是处理错误的逻辑,上报的计时器不能立刻开启,因为,此时jquery 还没加载,
计时器开启放在至多jquery加载实现之后。

2.肯定要做好处理错误局部代码的容错解决,不然业务逻辑代码还没报错,处理错误的局部反而报错就不好了。

3.当你间接双击html,在浏览器关上时,谬误收集机制可能不会正确工作,例如没有行号,列号,文件名,错误信息仅仅是Script Error,这是因为onerror MDN

当加载自不同域的脚本中产生语法错误时,为防止信息泄露(参见bug 363897),语法错误的细节将不会报告,而代之简略的**"Script error."**。在某些浏览器中,通过在<script>应用 `[crossorigin](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script#attr-crossorigin)` 属性并要求服务器发送适当的 CORS HTTP 响应头,该行为可被笼罩。一个变通计划是独自解决"Script error.",告知谬误详情仅能通过浏览器控制台查看,无奈通过JavaScript拜访。

解决形式为:服务端增加Access-Control-Allow-Origin,页面在script标签中配置 crossorigin="anonymous"。这样,便解决了因为跨域而带来的问题。

五、残缺代码

<!DOCTYPE html><html><script>    //处理错误的命名空间    window['errorSpace'] = {        ErrorTimer: null, //全局谬误上报计时器        ErrorArg: [], //全局谬误存储数组        ErrorMap: {}, //用于谬误筛选的对象        //存储错误信息        PushError: function (key, errMsg) {            let _ErrorMap = window.errorSpace.ErrorMap;            let _ErrorArg = window.errorSpace.ErrorArg;            //筛选            if (_ErrorMap.hasOwnProperty(key)) {                //筛选到雷同的谬误,可将值加一,能够判断谬误呈现的次数                _ErrorMap[key] += 1;                return;            }            //阈值            if (_ErrorArg.length >= 100) {                return;            }            //存储谬误            //对于要发给后端的数据,可依据需要组织,数据结构            _ErrorArg.push({                errMsg: errMsg || '', //错误信息                ver: '', //版本号                timestamp: new Date().getTime(), //工夫戳            });            //寄存错误信息的数组的阈值            if (Object.keys(_ErrorMap).length >= 100) {                //达到阈值之后,清空去重对象                _ErrorMap = {};            }            _ErrorMap[key] = 1;        },        //谬误上报函数        ErrorSend: function () {            clearTimeout(window.errorSpace.ErrorTimer);            let _ErrorArg = window.errorSpace.ErrorArg; //后面提到的全局谬误存贮数组            let _ErrorArgLength = _ErrorArg.length;            if (_ErrorArgLength > 0) {                let _data = []; //要发送的错误信息,因为是一次性发5条,放零时数组中。                //组织要发送的错误信息                for (let i = 0; i < _ErrorArgLength; i++) {                    if (_data.length >= 5) {                        break;                    }                    _data.push(_ErrorArg.shift());                }                if (_data.length) {                    //发送错误信息                    //jq ajax                    var _d = {                        type: 'POST',                        url: '',                        data: _data || null,                        dataType: 'JSON',                        contentType: 'application/x-www-form-urlencoded',                        success: function (data, status, xhr) {                            //上报失败,将谬误从新存储                            //这是假如服务端返回的数据结构是{status: 200}                            if (data.status !== 200) {                                //失败                                try {                                    //间接存入                                    //此处没有对_ErrorArg的长度进行判断,所以会溢出一次,使得谬误谬误尽可能的保留,问题不大,也能够不让溢出                                    _ErrorArg.push(..._data);                                } catch (error) {                                    console.log(error);                                }                            }                        },                        error: function (xhr, type, error) {                            //上报失败,将谬误从新存储                            try {                                //间接存入                                //此处没有对_ErrorArg的长度进行判断,所以会溢出一次,使得谬误谬误尽可能的保留,问题不大,也能够不让溢出                                _ErrorArg.push(..._data);                            } catch (error) {                                console.log(error);                            }                        }                    }                    $.ajax(_d);                }            }            //计时器距离,当数组长度大于20时,一秒执行一次,默认2秒一次            let _ti = _ErrorArgLength >= 20 ? 1000 : 2000;            window.errorSpace.ErrorTimer = setTimeout(window.errorSpace.ErrorSend, _ti);        },    };    //谬误收集    window.addEventListener("error", e => {        if (!e) {            return;        }        let _err_msg = ''; //要上报的错误信息        let _r = 0; //产生谬误的行号        let _l = 0; //产生谬误的列号        let _fileName = ''; //产生谬误的文件名        const srcElement = e.target || e.srcElement;        if (!srcElement) {            return;        }        if (srcElement === window) {            //语法错误            let _error = e.error;            if (_error) {                _err_msg = _error.message + _error.stack;                _r = e.lineno || 0;                _l = e.colno || 0;                _fileName = e.filename || '';            }        } else {            // 元素谬误,比方援用资源报错            if (srcElement.src) {                _err_msg = srcElement.src;                _fileName = srcElement.src;            }        }        let _key = `${_fileName}_${_r}_${_l}`;        window.errorSpace.PushError(_key, _err_msg);    }, true);    //解决console.error;    let _windowError = window.console.error;    window.console.error = function () {        let _str = JSON.stringify(arguments);        window.errorSpace.PushError(_str, _str);        _windowError && _windowError.apply(window, arguments);    }</script><head>    <meta charset="utf-8">    <link rel="stylesheet" href="">    <link rel="stylesheet" href=""></head><body></body><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>    //开始谬误上报计数器    window.errorSpace && window.errorSpace.ErrorSend && window.errorSpace.ErrorSend();    </script></html>