前端异常监控
自行开发sdk嵌入project进行:异常报错、性能分析、行为统计;
自研流程
调研分析 => 数理核心难点 => 规划开发计划 => 乌拉
分析各大平台sourcemap异常定位实现方案
Fundebug
默认情况下,Fundebug会根据压缩代码中的sourceMappingURL下载Source Map文件,用户仅需要将生成的Source Map文件部署在服务器上,不需要额外操作。
如果用户不希望公开Source Map,则可以主动上传Source Map文件。Fundebug提供了两种不同的上传方式:
11
- 通过Fundebug的前端UI上传
- 通过Fundebug的API上传
Webfunny
公司成立于2021年05月25日,我们致力于帮助前端工程师定位并解决各种线上问题,确保项目健康良好的运行。提供异常数据及性能数据的查询,及提供用户行为记录数据,个人免费版本只有2周,目前对部分小程序还未支持。
- 优点:提供各种大屏可视化数据展示
- 缺点:公司级产品需付费,平台支持还不够完善
异常定位实现:
- 开启minimize、source-map打包后部署到生产环境。
- 部署上产以后,在关闭minimize代码混淆压缩,再次打包,保存这次打包后的代码
- 遇到问题使用生产环境定位的位置信息,在本地进行源码定位
frontjs
frontjs是蒲公英旗下一款多个维度监测网站的产品,除开对异常事件的监控,还增加了性能,访问数据,留存,日报等功能。但默认的基础版本无用户范围和性能监控,且数据保留24小时,高级版和私有化部署都需要额外收费。
- 优点:异常和性能监控都区分了类型,网络请求和资源下载等;除开对异常事件的监控,还增加了性能数据等监控,功能较丰富。
- 缺点:基础版本不够用,升级需付费,且异常监控无行为记录,错误记录较为表面。
sentry
sentry是一个开源的
监控系统
能支持服务端与客户端的监控,还有个强大的后台错误分析、报警平台。主要用于如何快速的发现故障。支持几乎所有主流开发语言和平台,并提供了现代化UI,它专门用于监视错误和提取执行适当的事后操作所需的所有信息,官方提供了多个语言的SDK。让开发者第一时间获悉错误信息,并方便的整合进自己和团队的工作流中。
- 优点:支持语言全面,功能较完善,开源免费/收费使用saas服务
- 缺点:外国服务器,需要考虑服务稳定性
异常定位实现:
大致有三种方式:
- 官方cli(sentry-cli)
- 调用官方提供的API(HTTP)接口上报
- webpack插件进行上报
当下产品需求
- 无感知收集:文件加载错误、js执行报错、接口异常
- npm私服搭建,从私服安装sdk
- 源定位的方案
- 将源码sourcemap与文件一同部署:加密混淆,限制ip访问
- 将源码sourcemap上传到私服:修改ks前端商店中的docker容器,增加是否上传sourcemap选项配置,在打包完成后进行上传,在删除sourcemap源码文件,最后部署到服务器
SDK开发流程
安装监控SDK
->
搜集错误->
存储错误(去重、err展示)->
分析错误(分类,字段映射)->
错误报警(上报)->
定位错误(B端管理后台映射错误位置)->
解决错误
过程自述
使用npm安装sdk,初始化SDK配置。sdk收集本地错误信息,错误信息分析整理用于上报接口。sourcemap上传有两种方式:
- 将sourcemap文件处理(加密)后部署到服务器上以便访问获取。
- 使用脚本在打包的时候将sourcemap文件上传到后端保存,删除原有的sourcemap文件再部署。
线上程序报错后,通过接口api将错误信息上报到后端服务。后端通过错误信息位置与获取到的sourcemap文件进行调用skd进行反向解析出源代码位置及代码片段,返回json数据给客户端进行展示。
graph TD
Start --> 安装SDK --> 打包上传源文件 --> sourcemap
sourcemap --> 部署到服务器通过外网下载访问 --> SDK上报错误
sourcemap --> 打包时上传到服务器 --> SDK上报错误
服务端 --> 保存sourcemap文件 --> 接收并保存报错信息 --> 调用source-map模块进行解析错误具体位置 --> 读取目标源文件 --> 截取代码片段并保存 --> 返回前端展示
前端 --> 检索错误信息 --> 展示错误详情
错误收集
第一个版本先集成到微信小程序中,也就是捕获js的异常问题.
我们常见的异常有:...(如下所示)...那么我们怎么捕获这些异常呢?
- 静态资源加载类型异常
- js 代码执行时异常
- promise 类异常
- 接口请求类型异常
- 跨域脚本执行异常
- log控制台error
web端
方式一:window.onerror
window.onerror是JS原生支持的错误捕获api。但是这个方法存在兼容性问题,在不同的浏览器上提供的数据不完全一致,部分过时的浏览器只能提供部分数据。有时候window.onerror拿不到详细的错误信息;
- 能捕获
- js 代码执行时异常
- 异步代码执行错误(setTimeout)
- 不能
- 被其他程序提前捕获的错误
- 跨域的JS资源
- 语法错误
- 资源错误,不能捕获
window.onerror其实是一个回调函数
,也就是发生异常的时候这个javaScript会执行window上的属性onerror,onerror是一个函数,默认如下:
window.onerror = function (message, url, lineNo, columnNo, error)
参数的含义:
message
{String} 错误信息。直观的错误描述信息,不过有时候你确实无法从这里面看出端倪,特别是压缩后脚本的报错信息,可能让你更加疑惑。url
{String} 发生错误对应的脚本路径,比如是你的a.js -报错了还是- b.js报错了。lineNo
{Number} 错误发生的行号:就是在url文件中的哪一行,如果代码混淆压缩过也是无法很好的阅读columnNo
{Number} 错误发生的列号。error
{Object} 具体的 error 对象,包含更加详细的错误调用堆栈信息,这对于定位错误非常有帮助。
对于window.onerror的补充
window.addEventListener('error', (error) => {}) 可以捕获:图片、script、css加载错误
window.addEventListener('error', (error) => { console.log('捕获到异常:', error); }, true)
复制代码
方式二、unhandledrejection
unhandledrejection捕获Promise类型错误
方式三、errorHandler : vue实例挂载错误回调事件
- 能捕获
- errorHandler只能拦截vue程序中抛出的且
没有被捕获
的错误;
- errorHandler只能拦截vue程序中抛出的且
- 不能捕获
- ssr模式下无法捕获。
初始化:
app.config.errorHandler = function (err, instance, info) {
// 处理错误,例如:报告给一个服务
console.log('app.config.errorHandler err:', err)
console.log('app.config.errorHandler instance:', instance)
console.log('app.config.errorHandler info:', info)
}
复制代码
小程序错误收集
-
// app.vue export default { onError: function (err,b){ console.log('App onError',err,'---',b) }, } 复制代码
-
小程序报错文件定位时,需要找到对应的
app-service.map.js
文件路径,来定位源码位置。- 通过
重写
wx的navigateTo
方法来获取页面跳转路由path - 在通过app.json文件路由结构来确定报错文件
app-service.map.js
所属的目录
- 通过
-
解决小程序报错信息中的文件行号和列号的提取
const stack = (stack) => { const lines = stack.split("\n"); // 报错信息 const newLines = [lines[0]]; // 逐行处理 for (const item of lines) { if (/.*(https?:\/\/.+):(\d+):(\d+)$/) { const arr = item.match(/@(https:\/\/.+):(\d+):(\d+)$/) || []; if (arr.length === 4) { const url = arr[1]; const line = Number(arr[2]); const column = Number(arr[3]); const filename = (url.match(/[^/]+$/) || [""])[0]; // console.log("url: ", url); // console.log("line: ", line); // console.log("column: ", column); // console.log("filename: ", filename); // console.log("---------------: "); newLines.push({ url, line, column, filename, }); } } } return newLines.join("\n"); }; const results = stack( `<TypeError: this.initData is not a function. (In 'this.initData()', 'this.initData' is undefined)>\nonShow@https://usr//app-service.js:5744:2010\nonShow@[native code]\npo@https://usr//app-service.js:5057:27522\ngo@https://usr//app-service.js:5057:27601\n@https://usr//app-service.js:5057:32581\n@https://usr//app-service.js:5057:54694\n@https://lib/WASubContext.js:1:775540\n@[native code]\n@https://lib/WASubContext.js:1:775333\n@https://lib/WASubContext.js:1:785609\n@https://lib/WASubContext.js:1:745394\n@https://lib/WASubContext.js:1:788116\n@https://lib/WASubContext.js:1:745394\n@https://lib/WASubContext.js:1:791446\n@https://lib/WASubContext.js:1:745394\nxr@https://lib/WASubContext.js:1:792142\n@https://lib/WASubContext.js:1:744498\n@https://lib/WAServiceMainContext.js:1:847062\nemit@https://lib/WAServiceMainContext.js:1:843813\nemit@[native code]\n@https://lib/WAServiceMainContext.js:1:2585312\n@https://lib/WAServiceMainContext.js:1:853487\n@https://lib/WAServiceMainContext.js:1:847161\nemit@https://lib/WAServiceMainContext.js:1:843813\n@https://lib/WAServiceMainContext.js:1:907321\n@https://lib/WAServiceMainContext.js:1:884248\nemit@https://lib/WAServiceMainContext.js:1:94817\nemit@[native code]\nemit@https://lib/WAServiceMainContext.js:1:94436\nsubscribeHandler@https://lib/WAServiceMainContext.js:1:97085\nglobal code@` ); console.log("解析结果:", results); 复制代码
遗留问题
- 暂无
错误类型
- ReferenceError:找不到对象时
- TypeError:错误的使用了类型或对象的方法时
- RangeError:使用内置对象的方法时,参数超范围
- SyntaxError:语法写错了
- EvalError:错误的使用了Eval
- URIError:URI错误
- Unexpected 不符合语法规范
总结
本文纯属自己个人观点,如有误解请评论指出,一同进步🙏🏻