小东西快快学快快记,大知识按计划学,不拖延
今天!我们来总结!上报页面错误数据!
言简意赅!不废话!
本文分为4个部分
1、页面错误分类
2、错误监听具体处理
页面错误分类
页面错误这种数据上报的重要性,想必不用我多说了吧
页面通常就分为3种错误
1、js 报错
2、资源加载错误
3、请求报错
其中js执行错误,会显示在控制台上,这也是比较常见的造成bug的原因。
一个多级不判空取值就很可能导致严重的白屏bug
你以为这种错误很少吗,就我们团队就这种bug就出现好多次,被大佬骂惨了,看看我们现在线上监控到的错误
一大半都是 of undefined,of null,is not a function 这些看似非常简单的错误
人有时候存在侥幸和偷懒心理,只觉得成功就行,习惯性忽略错误情况
我大佬常说的一句话,我们要对代码抱有敬畏之心
不说废话了
下面来说下具体如何监听这3种错误分类
监听JS 报错
JS 的抛错,分为 JS 执行错误 和 未被 catch的 promise 错误,他们分别需要监听不同的事件来捕获他们的错误
1JS 执行错误
我们会劫持 window.onerror 事件,如下,重写然后加上自己的处理逻辑
const orgError = window.onerror;
window.onerror = (...args) => {
// 上报获取错误信息处理逻辑....
orgError?.call(window, ...args);
};
看下这个函数args都包含了什么(以下按照参数顺序列出)
按字面意思来看已经可以理解了,我们来看一下实际捕获的错误的这个五个参数
其中还有一个比较重要的信息是 第五个参数的 error 对象
里面包含了 调用栈信息,就上面你看到的这些,我给格式化一下
"ReferenceError: userinfo is not defined
at getuserINfo (http://127.0.0.1:5500/CODE/LOGGER/PAGE_ERROR/index.js:69:3)
at JSError (http://127.0.0.1:5500/CODE/LOGGER/PAGE_ERROR/index.js:64:3)
at http://127.0.0.1:5500/CODE/LOGGER/PAGE_ERROR/index.js:87:1"
可以看到所有的函数调用栈,getuserInfo 和 JSError
上报什么数据
除了我们常规的上报基础数据
如你上面看到的数据,都需要上报上去
可以看一下我们监控系统最终上报的数据
我们具体是把这些数据 拼接成一个字符串 ,然后进行上报
问题一览
1、无法获取跨域 js 详细错误
如果你的js文件和引入的页面域名不一致,产生的跨域问题,就会导致无法捕获到详细错误。
只能拿到 Script error,具体参数如下
一般js文件都放在cdn,所以不可避免会跨域的,我们的解决办法也就是解决跨域问题
1.文件添加跨域头 Access-Control-Allow-Origin
2.引入js的script标签加上属性 crossorigin="anonymous"
2、向上抛错
在重写 window.onerror的时候,如果不想继续抛错(捕获之后不显示控制台)
那么就在回调后面return true
但是一般不会这样的,我们是只做拦截,保持原样,否则会对开发者不友好
3、无法捕获语法错误
并不是什么错误都能捕获到,语法错误就不可以比如你乱用关键字
const function = 1
语法错误,可能代码文件解析中断,监听代码当然没有生效
4、根据行列号利用 sourcemap 还原源码位置
这里详细讲又是一大篇了,具体会另外写篇文章总结
可以简单描述一下
我们团队用了sentry 去做这个事
1、项目打包的时候,会把sourcemap上传到 sentry 系统
2、系统根据上报的错误信息就会还原具体的报错位置
比如这样
2未被 catch的 promise 错误
我们还需要监听捕获没有被catch的promise
比如这样
控制台就会显示
具体我们会监听 unhandledrejection 事件来捕获这个错误
window.addEventListener('unhandledrejection', (e)=>{
// 上报获取错误信息处理逻辑....
});
看下回调内的事件对象,主要 reason 这个属性,包含了没有 catch 的 错误信息
上报什么数据
除了基础的上报数据,这里我们就只需要把 reason 错误信息字段上报上去就行了
问题一览
1、未被catch的 promise 错误,不是指 promise 内的执行 错误
比如下面 promise 中 读取了一个没有声明的变量 aaa
new Promise((res, rej) => {
setTimeout(() => {
console.log(aaa);
rej('错误');
}, 1000);
});
这个错误属于 js 执行错误,不属于 promise 错误
所以它会被前面的 window.onerror 捕获到,而不会触发 unhandledrejection 事件
资源报错
监控资源报错我们在另一篇内容有总结,具体可以看 【前端监控】静态资源测速&错误上报
这里再简单描述下
前面我们用window.onerror 来监听js执行错误,但是它并不能获取到资源加载失败的错误,因为这些错误不会向上冒泡,但是我们可以进行捕获
所以我们可以使用 addEventListener 的方式设置捕获监听错误
这里的话可以两种方式
window.addEventListener('error',handler,true)
window.document.addEventListener('error',handler, true)
什么区别呢
window.addxx 可以监听到 js执行错误 + 资源加载错误window.document.addxx 只监听到 资源加载错误
而js执行错误我们已经通过window.onerror拿到了,这里没必要又重复获取一遍。
并且需要区分出错误类型,所以我们这里只监听资源错误就好了
window.document.addEventListener('error',handler, true)
请求报错
请求报错的内容,也已经写过,具体可以参考 【前端监控】自动抓取接口请求数据
简单说,就是 劫持 XMLHttpRequest 和 fetch 方法,在原来的方法上包一层自己的处理逻辑,拿到请求的信息 等
而 判断 请求是否出错,是根据 请求的 status 来判断的
具体标准的 HTTP status code 如下
Informational responses (100–199)
Successful responses (200–299)
Redirects (300–399)
Client errors (400–499)
Server errors (500–599)
如果 status 在 400 以上,我们就认为请求是错误的
另外,在请求完成前,status的值为0。如果 XMLHttpRequest 出错,浏览器返回的 status 也为0,所以0 的情况也要兼容下
另外,请求超时也算错误,我们需要额外判断超时的情况
现在以 xhr 为一个例子说明一下
// ...... 其他劫持逻辑
const originSend = window.XMLHttpRequest.prototype.send;
window.XMLHttpRequest.prototype.send = function () {
xhr.addEventListener('loadend', function () {
let type = ''; // 错误类型
// 超时错误
if (this.isTimeout) {
type = 'timeout';
// 在请求完成前,status的值为0。如果 XMLHttpRequest 出错,浏览器返回的 status 也为0。
} else if (this.status <= 0) {
type = 'failed';
// Client errors (400–499) Server errors (500–599)
} else if (this.status >= 400) {
type = 'error';
}
this.report({
type,
url,
method,
body,
duration,
});
});
xhr.addEventListener('timeout', () => {
xhr.isTimeout = true;
});
return originSend.apply(this, arguments);
};
// ......
最后可以看下我们对于线上页面监控的一个异常数据对比图,大概长这样(数据是假的)
可以很清楚看到线上页面的稳定性,一个字,稳
最后
鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵, 如果有任何描述不当的地方,欢迎后台联系本人,领取红包