有一天一个测试同事的一个移动端页面白屏了,看样子是页面哪里报错了。 我本身打开页面并无报错,最后发现报错只存在于他的手机,移动端项目又是在微信环境下,调试起来会比较麻烦,最后用他手机调试才发现问题: 是他帐户下面有个对话的消息数据有问题致使页面报错了。 通常遇到这种状况只有用他的手机或者帐户调试能很快查到问题,若是是外部的用户怎么办,我无法拿他的手机去测试。javascript
其实这个问题很常见,可是此次我以为这个问题若是不是咱们本身同事发现的,那就很恐怖,可能废很大精力才能查出问题,甚至会致使很严重的线上bug,细思极恐,恰好前不久成都FCC的大前端交流会上叶小钗谈到了监控这块,也让我有所启发,这些公共服务才是公司的核心财富,目前公司业务发展处在上升阶段,将来用户确定会愈来愈多,对系统的稳定性要求也会愈来愈高,那既然咱们还缺少这块的服务,如今作正合适。php
从提出这个想法的一开始就知道,落地才是关键,不然一切空谈。 恰好半个多月之后,咱们前端组须要在公司作一次分享,我如今作个题材就挺适合分享的,其余后端和测试同事也容易听进去一点。 最开始我考虑了后端存储和可视化的状况,想找个现成后端集成工具帮我处理后端的工做。 就找后端同事问了一下,同事推荐了 Elasticsearch+Fluentd+Kibana 。 而后稍微研究了一下,总以为哪里不对,反正研究了以后发现可能仍是须要作一些定制开发才能解决需求,后端同事听了个人需求也是这么说的。 一人之力有限,而且公司业务上的事情也多,找一个后端同事配合极好,利用各自的优点能够更快落地,这样我也能够专一前端的工做和把控整个项目落地。 就这样,我和后端同事商量了一下,他也答应抽空和我一块儿搞了。 抛开后端的事情,我开始思考前端的工做,去调研一下别人的方案和这块的知识。 有一些三方库或者开源项目提供相似的功能的,作了很简单的了解。 最后想着本身开发更容易去适应自身的业务,而且目前初版的需求功能也并无那么大的开发量,那就本身作吧。 前期碰见了一些须要解决和实现的功能点: 生成sourcemap,监听js报错和信息上报,压缩的js代码上报后sourcemap解析问题,如何更平滑的应用在业务项目中,数据存储优化等。html
前端前端
后端java
经过onerror咱们能监听和拿到js的报错信息, 能够拿到以下代码的五个参数。 columnNo, error这两个参数在一些老版本的IE8-9浏览器和opera低版本等浏览器上可能拿不到,可是没有关系,咱们在代码上兼容拿不到参数的状况,若是缺乏后两个参数,传空值就好了。 也能够经过其余方式拿到这些老版本浏览器的columnNo和error参数,目前监控主要是针对移动端,也没太大必要去兼容老版本的浏览器。node
window.onerror = function (msg, fileUrl, lineNo, columnNo, error) {}
onerror方法大体实现以下:nginx
可能存在跨域问题,不一样域下的js须要配置script属性 crossorigin="anonymous" 和后端配置 Access-Control-Allow-Origin,可是目前咱们的项目不存在js跨域问题。chrome
提示一下onerror并不能拿到全部报错信息,好比网络报错等。如今咱们能经过onerror拿到报错信息了,但是线上的代码是通过压缩的,报错的时候咱们能拿到的的行列数和变量命都不能告诉咱们源代码哪里出错了。这里咱们须要用到sourcemap,下面来说讲它。数据库
sourcemap就是一个信息文件,里面储存着位置信息。 也就是说,sourcemap文件记录了代码转换前的位置和转换后对应的位置(www.ruanyifeng.com/blog/2013/0…)。 下面图1是login.js的压缩版本,第二行的注释指定了map文件的相对路径,浏览器根据注释会找到map文件而后自动解析出来,在调试器里就能够看到源码了; 图2是map文件(json格式); 图3图4介绍sourcemap文件。 图2咱们生成的map文件sourcesContent字段直接引入了源文件代码(构建工具能够配置是否给map文件引入源文件),这样能够方便后端解析,若是没有源文件对应的话后端是解析不出正确结果的。json
(图1)
(图2)
(图3)
(图4)
咱们的移动端项目构建工具比较老了,统一用的grunt做为打包工具。 以前没有在压缩代码时使用sourceMap,由于开发和测试环境没有压缩,因此也不须要在浏览器用sourceMap调试。 而后我就再去修改gruntfile文件(以前不是我写的),sourceMap配置感受和官方文档对不上,总是报错,最后才发现以前的打包工具的依赖版本是13年的了,也暂时不必去折腾版本问题了,把老版本的文档翻出来再配置了一下sourcemap文件就成功的生成在源文件的同级目录下了,好比源文件叫xx.js,map文件就是xx.js.map。 咱们给js文件加上了md5版本号,因此实际的文件是xx.md5.js和xx.md5.js.map(md5是根据内容变化的)。
思考的时候发现最大的难点应该在sourcemap解析。 最开始后端同事觉得sourcemap是nodejs生成的文件,他们后端用的go或者php彷佛不能解析吧,若是知道了sourcemap原理就应该知道,它只是一种数据格式和开发语言不要紧。 我把map文件和报错信息交给后端同事,他们用go语言的一个工具成功解析出了答案,实现了本地文件的解析。 可是咱们须要的是自动化解析,不可能每次都去把存储的报错信息手动的拿出来再去找对应的map文件作人工解析。 因此须要咱们后端程序本身去找到map文件,并解析报错信息。
如此一来,后端解析存在两个关键问题:
这里只说咱们的方案,map文件和源js文件打包到同级目录下,一块儿上传到服务器(好比js的路径是www.xxx.com/dist/index.md5.js,那map文件的地址就是www.xxx.com/dist/index.md5.js.map),服务端就能够根据报错的js路径再加上.map后缀找到map文件。 压缩文件有一段注释描述sourceMappongURL指定了map文件的位置,打开浏览器以后调试器会找到这个map文件,在浏览器里就能看到源代码,为了不这种状况,须要服务器配置 .js.map 后缀的文件不可访问。 若是这样的话,服务器解析的时候不能直接去下载静态资源.map文件,而是须要去找到服务器本地对应的map文件,这样要单独配置路径和写逻辑很麻烦,并且文件夹结构有变更的话也不灵活。 因此咱们的方案是作token权限校验,map文件必须加正确的token参数,服务器才会返回资源(xxx.js.map?token=xxxx),不然nginx会屏蔽没有token或者token错误的请求。
两种方法,一种是后端接口收到报错信息以后,立刻找到map文件,并解析存储到数据库。 一种是先保留上报信息,经过接口查询的时候再去解析。 咱们选择了前者,接口收到数据以后,后端根据当前报错文件的url,去查查本地是否已经下载过当前文件,若是已经存在这个文件,就直接用本地的文件解析,若是本地没有,路径加上.map和token参数,下载对应的map文件到本地,而后再去读取当前本地文件并解析,解析的数据和上报的数据就存为一条记录。 若是是后者的方法,存在不少麻烦的问题,这里很少说了。
一张图详细描述咱们的解析流程:
有一种状况可能发生: 当前项目已经更新到1.1版本了,1.0版本的一个报错之前没被触发,这个时候有个用户缓存了1.0版本的代码,而且触发了一个新的报错,这个时候服务器本地存储的map文件里没有这个文件,就会带上token去下载map文件,由于当前已是1.1版本了,原js文件发生过变更,md5的版本已经对应不上了,这个时候就无法找到map文件了,没法解析,因此这种特殊状况只能存储上报的errorInfo信息。
目前js的onerror方法只有代码量不大,后期还会有叠加。如今的想法是尽可能不和业务代码作过多接触,只须要直接引入当前js到各个业务项目中去,每一个项目不用对它太多任何配置,让它尽可能单纯一点。
后期是会作管理后台来查询和统计这些异常日志的,同一个错误可能上传报错数据到服务端,后端查询出来是一条条独立的记录,咱们不能区分这条记录的报错是否是有重复数据,也不该该让后端去作字段对比。 后来想到给 报错的文件路径+行+列 信息拼在一块儿字段作md5生成,根据这个惟一值生成md5,最后查询的时候只须要查询当前md5字段就能知道这一条报错一个有多少条记录。 不过我想的太天真了,不一样的浏览器报错行列信息有点不同,同一报错就可能生成不一样的md5字符串,即使这里有点问题,我仍是继续用这个方案保存了md5(由于内核缘由,移动端的差别仍是比较小,当前字段也能有必定的区分性)。
咱们初版存储的主要数据(还有一些常规的就不说) :
{ "businessInfo": "{}",//业务项目自定义的数据 "errorMd5": "80bb86b86da0607c0dc5c3a77e16eab6",//根据报错部分信息生成的md5 "manualSendError": "{}",//手动上传的报错信息 "pageUrl": "http://www.xxx.com/xxx.html",//放生报错的页面url "parseError": true,//解释是否失败 "parsed": '{"col":0,"errKey":"list","file":"xxx.js","line":105}',//解析后的行列、文件路径和变量 "raw": '{"msg":'', "fileUrl":'', "lineNo":'', "columnNo":'', "error":''}',//onerror的五个参数 "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4X Build/MMB29M; wv)..." //navigator.userAgent }
邮件提醒是颇有必要的一个功能,目前已经实现实时邮件提醒功能。 公司企业邮箱建个单独的邮箱就叫frontendmonitor@吧,当后端接口收到报错后,把解析数据经过这个邮箱发送给前端,达到提醒效果。 若是是用QQ邮箱或者我的邮箱应该须要在帐户里开启smtp服务,QQ企业邮箱是默认开启此功能的。 邮件功能要注意性能和优化问题,不能由于前端报错太多致使服务器挂掉。
这种非业务服务,来源于我的兴趣和思考,并无上层压力须要你作或者何时作完。 从最开始有个想法、去调研、去找后端同事求助、 开干到最终落地。 这个过程须要本身坚持作下去,由于惧怕本身不能最终落地,因此抓紧时间,一步步去实现每一个细节的想法,让事情尽快落地和上线,以避免本身对这个事情越拖越久。 做为需求方,更好的把握整个项目,加上本身的兴趣,因此此次本身也学习了一点go语言,保证能看懂后端代码和了解后端逻辑,最好能作一点开发,此次在后端同事代码的基础上,实现了发邮件的小功能,我称之为浅入浅出,装完逼就跑路~ 如今初版已经上线,而且在刚上线不到两个小时,就收到了报错邮件,吓得我急忙查找bug,很快查出来了问题来,这个bug应该存在好久了,可是由于没有阻塞性,而且没有影响到业务,也一直没被发现,结论是咱们这个前端异常监控功能仍是很成功! 后期还有不少功能须要开发,统计、数据可视化、智能报警等等。 初版落地,就为之后的迭代和进化打下了良好基础。
在作这个事情的过程当中,我是想尽快把事情落地,时间也很紧张,也并无作很是充分的调研,好比现成的一些开源项目是怎么作的。 后来从同事那里了解到 sentry 这些三方开源项目以后,也有一点失落过,虽然我也解决了个人需求,可是三方的开源项目是一个很是完善的系统,提供了不少功能,比我这个强大多了,那我作这个到底有什么意义, 感受彻底和别人比拼不上,将来我这个项目会继续迭代吗,有继续迭代的必要吗? 之后有特殊定制化的需求的时候,也许本身开发的才容易更适应业务,但是有那个机会吗? 这一次落地已经达到我最初的要求了,也能帮我解决目前问题,将来还有不少挑战和迭代等待着,我会带着它一路过关斩将,仍是半路死掉? 我想说:
最后大力地感谢我司后端同事的大力支持!!~
关注大诗人公众号,第一时间获取最新文章。