前端错误与异常数据上报 - WGTracker

1,060 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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); // 异常堆栈信息
  // ...
  // 异常上报
};

image.png

image.png

通过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 头。

image.png

  • 在引入 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}`);
}
``

image.png

image.png

小结

image.png

sourceMap

通常在生产环境下的代码是经过webpack打包后压缩混淆的代码,所以我们可能会遇到这样的问题:我们发现所有的报错的代码行数都在第一行了。

解决办法是开启webpack的source-map,我们利用webpack打包后的生成的一份.map的脚本文件就可以让浏览器对错误位置进行追踪了。此处可以参考webpack document

其实就是webpack.config.js中加上一行devtool: 'source-map',如下所示,为示例的webpack.config.js:

image.png

image.png

主动上报异常

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上报的错误类型Enum1、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/_axios@0.24.0@axios/lib/core/createError.js:16:15)\n    at settle (webpack-internal:///./src/common/node_modules/_axios@0.24.0@axios/lib/core/settle.js:17:12)\n    at XMLHttpRequest.onloadend (webpack-internal:///./src/common/node_modules/_axios@0.24.0@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/_axios@0.24.0@axios/lib/core/createError.js:16:15)
    	at settle (webpack-internal:///./src/common/node_modules/_axios@0.24.0@axios/lib/core/settle.js:17:12)
    	at XMLHttpRequest.onloadend (webpack-internal:///./src/common/node_modules/_axios@0.24.0@axios/lib/adapters/xhr.js:66:7)",
	"Info":"created hook (Promise/async)",
}

错误信息在localstorage中的存储结构

时间浏览器用户名分辨率日志错误提示信息日志类型statusdescStack发生页面工作站
2022-1-09 16:23:45IE 11.0杨毅成1440x900Request failed with status code 404接口报错404apiDesc【接口的具体报错内容,见下描述】【objectString堆栈信息】
JS异常jsDesc【接口的具体报错内容,见下描述】
自主上报customDesc【接口的具体报错内容,见下描述】

apiDesc

参数名称关键字类型
关键错误信息描述messageString
请求headers信息headersObject
请求参数信息paramsObject
请求url地址urlString
请求方式methodEnum

customDesc

参数名称关键字类型
关键错误信息描述messageString
唯一错误标识,用于定位代码块statusString

jsDesc

参数名称关键字类型
错误的捕捉方式是否为onErrorisOnErrorEnum
关键错误信息描述errorMessageString
报错定位地址 - SourceMapscriptURIString
报错行号lineNoNumber
报错列号columnNoNubmer
错误详细堆栈信息errorStackObjectString
Vue下对错误的描述补充InfoString