最近在写谬误上报,记录一下,如果对你有所帮忙,荣幸之至;第一次写,有点啰嗦,见谅!
大略分为三个局部:
- 谬误收集
- 谬误筛选
- 谬误上报
- 注意事项
- 残缺示例
一、谬误收集
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);
对于语法错误,能够依据报错的文件名,行号,列号,组成 key
let _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>