持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
前言
众所周知,在后端的事物处理中。一旦有任何异常对应的开发同学就马上收到报警,并且第一时间处理
但是对于前端来说,往往是实际用户那里的脚本报错后才知道页面出现异常,往往这这时候已经是故障了
为了让前端也能和后端一样,需要将线上的 JavaScript 代码监控起来,当用户端浏览器出现异常前端第一时间被通知到
异常类型
- JavaScript异常错误:JS代码在执行层面造成的页面逻辑错误和边界值溢出等
- 主动上报异常:研发同学在开发过程中对代码块容错后主动抛出异常
- 接口调用错误:api接口除了响应值200意外的所有异常请求错误
JavaScript异常错误
正常情况下,对于JS的异常错误,可以通过全局监听异常来捕获,通过window.onerror或者addEventListener,看以下例子:
window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
console.log('errorMessage: ' + errorMessage); // 异常信息
console.log('scriptURI: ' + scriptURI); // 异常文件路径
console.log('lineNo: ' + lineNo); // 异常行号
console.log('columnNo: ' + columnNo); // 异常列号
console.log('error: ' + error); // 异常堆栈信息
// ...
// 异常上报
};
复制代码
通过window.onerror事件,可以得到具体的异常信息、异常文件的URL、异常的行号与列号及异常的堆栈信息,再捕获异常后,统一上报至我们的日志服务器(目前我们暂存本地localStorage)
亦或是,通过window.addEventListener方法来进行异常上报,道理同理。
onError 在 IE6 开始就支持了,所以 WGTracker 的主动采集是使用的 onError。
onerror常见问题 - 跨域JS脚本无法准确捕获异常
原因是浏览器的同源性策略(CORS),在高级浏览器中如果浏览器捕获到了错误信息,如果 JS 文件所在的域名(如:172.28.0.7)和当前的页面地址(如:127.26.0.9)是跨域的,那么浏览器会把 onError 中的 msg 替换为 script error,其余字段也会做替换。
目前我们的资源是在同源域名下,但是难保以后会出现跨域的情况,这里也需要为将来出现的情况做一下特殊处理:
webkit 的源代码
Script error 这个问题也是目前大家最诟病的一个问题:当报警发生之后,我们只知道有问题,但是很难知道哪里出了问题。
其实是有解决方案的:
- 首先 JavaScript 请求的 http 返回头上需要加上一个 Access-Control-Allow-Origin 头。
- 在引入 JavaScript 文件的时候需要在 script 标签上添加 crossorigin 属性,在加这个属性前请一定确保上一条已经完成,否则 JavaScript 文件不会执行!!!。
<script type="text/javascript" src="/public/theme/less.min.js" crossorigin></script>
<script type="text/javascript" src="/public/theme/font.js" crossorigin></script>
复制代码
Vue捕获异常
根据官方资料,在Vue中,异常可能被Vue自身给try ... catch了,不会传到window.onerror事件触发。
使用Vue.config.errorHandler这样的Vue全局配置,可以在Vue指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和Vue 实例。
Vue.config.errorHandler = function(err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
// 从 2.6.0 起,这个钩子也会捕获 v-on DOM 监听器内部抛出的错误。另外,如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数),则来自其 Promise 链的错误也会被处理。
// console.log(vm.$el.innerHTML);
console.log(`Error: ${err.toString()}\nInfo: ${info}`);
}
``
复制代码
小结
sourceMap
通常在生产环境下的代码是经过webpack打包后压缩混淆的代码,所以我们可能会遇到这样的问题:我们发现所有的报错的代码行数都在第一行了。
解决办法是开启webpack的source-map,我们利用webpack打包后的生成的一份.map的脚本文件就可以让浏览器对错误位置进行追踪了。此处可以参考webpack document。
其实就是webpack.config.js中加上一行devtool: 'source-map',如下所示,为示例的webpack.config.js:
主动上报异常
onError 的方案会采集到全面的浏览器报错,但是太全了,没办法指定场景下精细定位问题,而且还会有各种奇奇怪怪的错误
- ISP 在页面中注入的脚本报错
- 插件问题(乱七八糟的插件也是一样的令人讨厌,注入奇怪的东西在页面中,引起报错)。
为了让采集到的错误更有价值,需要暴露接口给让前端门自己在代码中上报异常(作为一名合格的代码工程师,一定要有抛异常的习惯呢)。
现在我们提供了自定义上报的接口,没错就是这么简单,你不用担心 WGTracker 脚本有没有加载,只要在你的代码中直接用就行了。
import { WGTracker } from '@utils/tracker.js'
try{
//your code
}catch(e){
WGTracker({
type:'custom'
error:{
status:[自定义的唯一标识,便于开发者定位问题],
message:'巴拉巴拉巴拉巴拉扒拉'
},
})
}
复制代码
PS:尽管try catch 对性能的影响微乎其微,但是一些用法会让性能受很大的影响, 在 try 语句块中不要定义太多的变量,最好是只写一个函数调用,避免 try 运行中变量拷贝造成的性能损耗。类似的不只是 try,定义 function 也是一样的。
接口调用错误
在项目中,我们的接口调用统一用基于axios封装的Https.js请求,在该文件中使用拦截器的方式对response信息拦截,同样使用我们提供的自定义上报接口上传接口调用错误信息
import { WGTracker } from '@utils/tracker.js'
/*
* 失败响应拦截
* */
interceptResponseFail(error) {
if(!error) {
return;
}
const errorObj = JSON.parse(JSON.stringify(error));
WGTracker({
type:'api'
error: errorObj
})
}
复制代码
WGTracker参数详情
参数名 | 描述 | 类型 | 可选值 |
---|---|---|---|
type | 上报的错误类型 | Enum | 1、js2、custom3、api |
error | 要上报的具体错误信息 | Object | 见示例 |
- error示例(api):
{
"message": "Request failed with status code 404",
"name": "Error",
"stack": "Error: Request failed with status code 404\n at createError (webpack-internal:///./src/common/node_modules/[email protected]@axios/lib/core/createError.js:16:15)\n at settle (webpack-internal:///./src/common/node_modules/[email protected]@axios/lib/core/settle.js:17:12)\n at XMLHttpRequest.onloadend (webpack-internal:///./src/common/node_modules/[email protected]@axios/lib/adapters/xhr.js:66:7)",
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": {},
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"headers": {
"Accept": "application/json, text/plain, */*",
"X-Auth-Token": "4dd382a0d0f248ba87c18e619c350565",
"systemUri": "umc",
"X-Preference": "%7B%7D"
},
"url": "/api/bcs/api/organization/getPageOrg123",
"method": "get",
"params": {
"currentPage": 1,
"pageSize": 100,
"clicDetailTypes": "1",
"orgCodes": "3036"
}
},
"status": 404
}
复制代码
- error示例(custom):
{
"message": "Request failed with status code 404",
"status": 404
}
复制代码
- error示例(JS):
{
"isOnError":true/false,
"errorMessage": "Request failed with status code 404",
"scriptURI": 'http://xxxx',
"lineNo":185,
"columnNo":1,
"errorStack":"Error: Request failed with status code 404
at createError (webpack-internal:///./src/common/node_modules/[email protected]@axios/lib/core/createError.js:16:15)
at settle (webpack-internal:///./src/common/node_modules/[email protected]@axios/lib/core/settle.js:17:12)
at XMLHttpRequest.onloadend (webpack-internal:///./src/common/node_modules/[email protected]@axios/lib/adapters/xhr.js:66:7)",
"Info":"created hook (Promise/async)",
}
复制代码
错误信息在localstorage中的存储结构
时间 | 浏览器 | 用户名 | 分辨率 | 日志错误提示信息 | 日志类型 | status | desc | Stack | 发生页面 | 工作站 |
---|---|---|---|---|---|---|---|---|---|---|
2022-1-09 16:23:45 | IE 11.0 | 杨毅成 | 1440x900 | Request failed with status code 404 | 接口报错 | 404 | apiDesc【接口的具体报错内容,见下描述】 | 【objectString堆栈信息】 | ||
JS异常 | jsDesc【接口的具体报错内容,见下描述】 | |||||||||
自主上报 | customDesc【接口的具体报错内容,见下描述】 |
apiDesc
参数名称 | 关键字 | 类型 |
---|---|---|
关键错误信息描述 | message | String |
请求headers信息 | headers | Object |
请求参数信息 | params | Object |
请求url地址 | url | String |
请求方式 | method | Enum |
customDesc
参数名称 | 关键字 | 类型 |
---|---|---|
关键错误信息描述 | message | String |
唯一错误标识,用于定位代码块 | status | String |
jsDesc
参数名称 | 关键字 | 类型 |
---|---|---|
错误的捕捉方式是否为onError | isOnError | Enum |
关键错误信息描述 | errorMessage | String |
报错定位地址 - SourceMap | scriptURI | String |
报错行号 | lineNo | Number |
报错列号 | columnNo | Nubmer |
错误详细堆栈信息 | errorStack | ObjectString |
Vue下对错误的描述补充 | Info | String |