当前位置: 首页 > 后端技术 > Node.js

说说前端异常捕获和上报

时间:2023-04-03 15:54:30 Node.js

关于微信公众号:前端呼啦圈(Love-FED)我的博客:Raub的博客知乎专栏:前端呼啦圈前言大家好,又见面了,这次给大家分享前端异常监控中需要了解的异常捕获和上报机制的一些要点,也包括实用性的参考代码和流程。首先,为什么我们需要捕获并报告异常?俗话说,经过大量测试和联调的项目,有时会出现非常隐蔽的bug。只有通过完善的监控机制,才能有效减少这种复杂且不可预测的问题。因此,对于直接面向用户的前端来说,异常捕获和上报是至关重要的。虽然市面上已经有一些非常完备的前端监控系统,如sentry、bugsnag等,但只有知己知彼,方能百战不殆。只有理解了其中的原理和逻辑,才能使用得心应手。异常捕获方法1.trycatch通常,为了判断一段代码是否存在异常,我们会这样写:try{vara=1;varb=a+c;}catch(e){//捕获并处理控制台。日志(e);//ReferenceError:cisnotdefined}使用trycatch可以很好的捕获异常并进行相应的处理,以免导致页面挂掉,但是也有一些缺点,比如需要在代码中对异常进行Wrapping会导致页面臃肿,不适合整个项目的异常捕获。2、与trycatch相比,window.onerror提供了全局监听异常的功能://异常信息console.log('scriptURI:'+scriptURI);//异常文件路径console.log('lineNo:'+lineNo);//异常行号console.log('columnNo:'+columnNo);//异常列号console.log('error:'+error);//异常堆栈信息};控制台日志(一);如图:window.onerror不仅提供了我们的错误信息,还提供了error可以准确定位行号和列号,看起来正是我们想要的,但是接下来就是填坑的过程了.异常捕获问题1.脚本错误。我们合理地尝试在本地页面上捕获异常,例如:这里为了优化加载我们把静态资源放在外域,但是抓取了异常信息为:经过分析,发现window.onerror无法捕获跨域后的异常信息,所以返回Scripterror。解决方法是配置脚本属性crossorigin="anonymous",在server-Origin中加入Access-Control-Allow。一般CDN网站会将Access-Control-Allow-Origin配置为*,即所有域名都可以访问过。2、sourceMap解决跨域或同域保存脚本后,可将代码压缩后发布。这时候就会出现压缩后的代码找不到原来的错误位置的问题。如图,我们使用webpack将代码打包压缩到bundle.js中://webpack.config.jsvarpath=require('path');//webpack4.1.1module.exports={mode:'development',entry:'./client/index.js',output:{filename:'bundle.js',path:path.resolve(__dirname,'client')}}最后我们页面引入的脚本文件是这样的this:!function(e){varo={};functionn(r){if(o[r])returno[r].exports;vart=o[r]={i:r,l:!1,出口:{}}...;所以我们看到的异常信息是这样的:lineNo可能是一个很小的数字,一般是1,而columnNo会是一个很大的数字,这里是730,因为所有的代码都被压缩到一行了。那么如何解决呢?聪明的童鞋可能已经猜到source-map开启了。是的,我们使用webpack打包压缩生成脚本对应的地图文件进行跟踪。在webpack中开启source-map功能:module.exports={...devtool:'#source-map',...}在打包压缩文件的最后会有这样的注释:!function(e){varo={};functionn(r){if(o[r])返回o[r].exports;vart=o[r]={i:r,l:!1,exports:{}}...;//#sourceMappingURL=bundle.js.map表示这个文件对应的map文件是bundle.js.map。下面是一个source-map文件的内容,是一个JSON对象:version:3,//Sourcemapversionsources:["webpack:///webpack/bootstrap",...],//转换前Filenames:["installedModules","__webpack_require__",...],//转换前的所有变量名和属性名mappings:"aACA,IAAAA,KAGA,SAAAC...",//记录位置信息文件的字符串:"bundle.js",//转换后的文件名sourcesContent:["//模块缓存varinstalledModules={};..."],//源代码sourceRoot:""//转换前的文件如果你想想了解更多sourceMap,可以去:JavaScriptSourceMap详细解释是这样的。既然我们已经获取到了脚本对应的map文件,那么如何分析获取文件压缩前的异常信息呢?下面报异常的时候再介绍这个。3.MVVM框架现在越来越多的项目开始使用前端框架。在MVVM框架中,如果要使用window。您的异常信息由框架自身的异常机制捕获。例如,在Vue2.x中,我们应该像这样捕获全局异常:Vue.config.errorHandler=function(err,vm,info){let{message,//异常信息名,//异常名script,//异常脚本urlline,//异常行号列,//异常列号stack//异常堆栈信息}=err;//vm是抛出异常的Vue实例//info是Vue特有的错误信息,比如错误所在的生命周期钩子在栈中可以找到信息,可以通过正则匹配得到,然后上报。同样,react也提供了异常处理的方法。React16.x版本引入了错误边界:classErrorBoundaryextendsReact.Component{constructor(props){super(props);this.state={hasError:false};}componentDidCatch(error,info){this.setState({hasError:true});//向服务器报告异常信息logErrorToMyService(error,info);}render(){if(this.state.hasError){返回“错误”;}返回this.props.children;}}那么我们可以这样使用这个组件:具体可以参考官方文档:ErrorHandlinginReact16Exceptionreporting上面介绍了前端的相关知识点异常捕获,那么既然我们已经成功捕获了异常,那我们应该如何上报呢?如果脚本代码没有压缩,可以直接抓取并上传相应的异常信息,这里不再介绍。下面主要说明处理压缩文件上报的常用方法。1.提交异常当捕获到异常时,我们可以将异常信息传递给接口。以window.onerror为例:window.onerror=function(errorMessage,scriptURI,lineNo,columnNo,error){//构建一个错误对象varerrorObj={errorMessage:errorMessage||空,scriptURI:scriptURI||空,行号:行号||空,列号:列号||空,堆栈:错误&&error.stack?错误堆栈:空};如果(XMLHttpRequest){varxhr=newXMLHttpRequest();xhr.open('post','/middleware/errorMsg',true);//上报给节点中间层处理xhr.setRequestHeader('Content-Type','application/json');//设置请求头xhr.send(JSON.stringify(errorObj));//发送参数}}2.SourceMap解析其实source-map格式文件是一种数据类型,既??然是数据类型就必须解析,目前市面上有相应的解析工具包。在浏览器环境或者node环境下,比较流行的是一个叫做“source-map”的插件。通过要求插件,前端浏览器可以解析地图文件,但是由于前端解析速度较慢,所以这里不推荐,我们还是使用服务端来解析。如果你的应用有node中间层,那么你可以把异常信息提交给中间层,然后解析map文件,把数据传给后台服务器。中间层代码如下:constexpress=require('express');constfs=require('fs');constrouter=express.Router();constfetch=require('node-fetch');constsourceMap=require('source-map');constpath=require('路径');constresolve=file=>path.resolve(__dirname,file);//定义post接口router.post('/errorMsg/',function(req,res){leterror=req.body;//获取前端传输过来的错误对象leturl=error.scriptURI;//压缩文件路径if(url){letfileUrl=url.slice(url.indexOf('client/'))+'.map';//映射文件路径//resolvesourceMapletsmc=newsourceMap.SourceMapConsumer(fs.readFileSync(resolve('../'+fileUrl),'utf8'));//返回一个promise对象smc.then(function(result){//解析原始错误数据letret=result.originalPositionFor({line:error.lineNo,//压缩行号column:error.columnNo//压缩列号});leturl='';//向上ReportURL//向后台报告异常//错误信息source:ret.source,//错误文件路径line:ret.line,//错误文件行号列:ret.column,//错误文件列号stack:error.stack//错误堆栈})}).then(function(response){returnresponse.json();}).then(function(json){res.json(json);});})}});module.exports=路由器;这里我们通过前端传递的异常文件路径获取服务端map文件地址,然后将压缩后的行列号传递给sourceMap返回的promise对象进行解析。我们可以通过originalPositionFor方法获取到原始错误行列号和文件地址,最后通过Ajax将需要的异常信息统一传输到后台存储完成异常上报如下图所示,可以看到控制台在解析后打印出了真正错误的位置和文件:附件:source-mapAPI3。注意点以上就是异常捕获和上报的主要知识点和流程,还有一些需要注意的地方。比如你的应用访问量很大,一个小的异常就可能把你的服务器挂掉,所以你可以在上报的时候过滤采样信息,设置一个控制开关,服务器也可以过滤类似的异常。请勿在一段时间内多次存放。另外window.onerror等异常捕获无法捕获到promise的异常错误信息,需要注意。最终的大体流程图如下:结束语前端异常捕获和上报是前端异常监控的前提。只有了解并做好异常数据的收集和分析工作,才能实现完善的错误响应和处理机制,最终实现数据可视化。本文详细示例代码地址:https://github.com/luozhihao/error-catch-report