在复杂的网络环境和浏览器环境下,自测、QA测试以及 Code Review 都是不够的,如果对页面稳定性和准确性要求较高,就必须有一套完善的代码异常监控体系。本文从三个方面进行阐述:1. 前端监控的类型; 2. 前端监控的处理流程(重点在于日志采集); 3. 相对成熟的监控系统。
前端监控的类型
当时去找日志分析项目的小哥哥聊天,他很不理解为什么前端还需要监控,聊天截图如下:
那么前端监控主要关心哪些类型的错误呢?个人总结如下,按优先级从高到底分(对,我就是那么专制!):
- JavaScript错误(分为语法错误和运行时错误,大头但非冤大头)
- css错误(如css文件404、css文件@import url的问题)
- 性能监控(总是被我们忽视的孩子,这次也一样)
前端监控的处理流程(针对js错误)
作为一个监控系统,通常有4大步骤:
- 采集
- 处理
- 分析
- 报警
如图所示:
从图中可以看出来,整个监控系统是需要前后端紧密配合的。纯前端这一块,重点在于日志采集。
日志采集的类型
收集日志的手段,可以归类为两个方面,一个是逻辑中的错误判断,为主动判断;一个是利用语言给我们提供的捷径,暴力式获取错误信息,如 try..catch 和 window.onerror。
1. 主动判断
我们在一些运算之后,得到一个期望的结果,然而结果不是我们想要的
这种属于逻辑错误/状态错误的反馈,在接口 status 判断中用的比较多。
2.try…catch捕获
判断一个代码段中存在的错误
以 init 为程序的入口,代码中所有同步执行出现的错误都会被捕获。
缺点:
- 没法捕捉try…catch块,当前代码块有语法错误,JS解释器压根都不会执行当前这个代码块,所以也就没办法被catch住(一般开发阶段就能检测出来)
- 没法捕捉到全局的错误事件,只有try…catch的块里边运行出错才会被你捕捉到。比如异步回调是不属于这个try…catch块的,所以没法被捕捉到
3. window.onerror
捕获全局错误:
在上面的函数中返回 return true,错误便不会暴露到控制台中。
注意点:
- onerror的代码块必须在所有js代码之前执行
- 对于跨域的js资源,window.onerror拿不到详细的信息,需要加上crossorigin属性,同时在服务端开启对静态资源的跨域访问配置。具体配置如下:
日志采集的问题
1. 压缩代码无法定位到错误的具体位置
线上的代码几乎都是经过打包压缩的,几十上百的文件压缩后打包成一个,而且只有一行。当我们收到 a is not defined 的时候,如果只在特定场景下才报错,我们根本无法定位到这个被压缩的 a 是个什么东西,那么此时的错误日志就是无效的。
第一个想到的办法是利用 sourceMap,利用它可以定位到压缩代码某一点在未压缩代码的具体位置。下面是 sourceMap 引入的格式,在代码的最后一行加入:
具体的sourcemap的VLQ编码和位置对应关系的讲解,请看阮老师的博客
2. 收集日志的量
没有必要将所有的错误信息全部送到 Log 中,这个量太大了。如果网页 PV 有 1kw,那么一个必现错误发送的 log 信息将有 1kw 条,大约一个 G 的日志。我们可以给 Reporter 函数添加一个采样率:
这个采样率可以按需求来处理,比如使用一个随机数,或者使用 cookie 中的某个字段(如 username)的最后一个字母/数字来判定,也可以将用户的 username 进行 hash 计算,再通过最后一位的字母/数字来判断。
3. 收集日志布点位置
为了更加精准的拿到错误信息,有效地统计错误日志,我们应该更多地采用主动式埋点,比如在一个接口的请求中:
上面我们精准地布下了三个点,描述十分清晰,这三个点会对我们后续排查线上问题提供十分有利的信息。
相对成熟的监控系统
1. fundebug
吴彦祖:小二,上图!
胡哥:客官,您看好了!(多图预警)
a) 控制台,展示了所有bug的趋势图和列表,可以对bug进行各种维度的过滤
b) 某个bug的详细情况,展示了这个bug的趋势图和各种采集的信息
c) bug详情之基本信息
d) bug详情之堆栈信息
e) bug详情之用户行为
胡哥:还有很多功能,团队管理、项目管理、bug分配、告警设置,就不一样介绍了,自己去看吧~
吴彦祖:不要忽悠我,生产环境打包压缩后,就只有一行文件了,就算报错也定位不到具体地方啊,有啥用?
胡哥:sourcemap听说过吧?您把这个文件上传上来,咱就能定位到具体代码了,不信你看
吴彦祖:好厉害哦,那我需要做什么呢?
胡哥:客官,您什么都不用做,把您那张吴彦祖的人皮面具抵押给我就能得到高质量的服务。
吴彦祖:如果我不是吴彦祖呢?
胡哥:哦,其他人都免费。只要注册一个账号,创建一个项目,就会自动生成项目的apikey,加这行代码就行了。
如果需要进行精细化配置,需要用js的方式,如:
还可以主动调用api报错
2. sentry
先附上sentry安装手册
吴彦祖:老规矩,上图
胡哥:得嘞
a) 错误列表,展示了所有bug,可以对bug进行各种维度的过滤
b) 某个bug的详细情况
c) 上传sourcemap
d) 上传sourcemap后对应的报错定位
胡哥:还有很多功能,团队管理、项目管理、bug分配、版本管理、告警设置等等~
吴彦祖:show me the code
胡哥:上个简单的配置,强大的配置请参考官网
3. fundebug vs. sentry
总的来说,fundebug对前端来说几乎是零配置,所有的复杂逻辑都交由后端智能化处理,如多个相同错误只显示一次,错误采样率为100%等;sentry的功能更强大,支持灵活的配置,但上手稍微困难一些。
前端监控路途的个人建议:
Step1: 从fundebug试手,体验一下前端监控带来的好处,以及实施过程中的问题,记录经验教训
Step2: 习惯了基础的前端监控及相应的流程后,自己搭建sentry服务端,进行适合项目的配置(采样率、需要过滤的规则、告警规则等)
Step3:了解sentry关于日志采集和错误定位的机制,自己写一套前端监控日志采集系统,与将来DI组的日志分析系统集成。
咱们以sentry为例,从零开始搭建一个sentry服务器。下文主要记载了在容器中搭建sentry服务器的步骤,以及使用sentry收集项目报错的过程中遇到的问题及解决方案。
在容器中搭建sentry
由于我们的网络环境是封闭的,只有内网之间的机器是互通的,所以那些敲几个命令就能在线安装的神话跟我们此生无缘。所幸时机不错,刚好接到QA团队开发的容器服务一期上线的通知,打算互为小白鼠试验一把,顺便提提产品建议(对的,被你发现了,我其实是个产品经理!)。
经过认真研读“容器服务”的产品文档,我开始了小心翼翼地探索。
- 申请仓库目录,直接使用已开放权限的test目录。
- 由于所谓的“docker官方基础镜像”也都是人为上传的,所以得在本地中转一下,先把sentry的镜像拉到本地,再把本地的镜像推送到电信私有镜像仓库。
- 由于sentry中的镜像有相互依赖关系,目前”容器服务”这个产品还不支持多依赖的容器管理,所以暂由QA姐姐代为启动
具体步骤
- 首先按照这篇文章的步骤,将sentry及其依赖的镜像下载到本地
- 配置本地docker环境,我本地是mac,具体的配置如下
- 在/etc/hosts中添加一行 xxip hub.chinatelecom.cn
- 在docker的配置界面中进行如下配置
- 点击restart重启docker服务
- 使用开发账号登录docker
- 本地镜像打标签,改为私有仓库的地址(由于sentry依赖于其他镜像,需要对每个镜像都做如下操作)
最终本地所有的镜像如下(红框那个是无效的,无法上传到私有仓库,建议取tag时不要有中间的/分隔):
- 上传更新标签后的镜像到远端仓库
- 在界面上查看是否已上传到远端仓库,我上传的9个镜像都已确认上传
- 退出登录
- 由于是多镜像依赖,目前暂不支持在界面上启动容器,所以启动容器的部分由QA姐姐代劳了
配置Sentry
- 首次登录时,会提示进行基础配置(我不会承认我因为多填写了一个/而找了一整天的跨域问题)
下图为错误示例,不要加斜杠!!!
- 进行邮箱服务器的配置,查看了一下页面上无法配置,只能修改yml配置文件。需要配置以下几项内容
该配置文件替换onpremise_web中的config.yml文件,重启onpremise_web容器服务,使配置生效。点击页面上的测试邮件,发现能顺利收到邮件。
正准备放开手脚邀请大家来试用的时候,发现邀请的邮件一直没有发出去。终于在网上搜到了解决方案,参考该方案的配置,明白了是多个容器都依赖于邮件服务器,我只修改了web容器的配置,另外几个容器的配置还是老的。所以只要把onpremise_base、onpremise_cron和onpremise_worker中的config.yml配置文件也改成上述配置文件,重启这些容器,就可以收到邀请邮件了。
Tips:
- 进入容器的命令
- 通过挂载目录传递外部文件到容器内部。具体来说,通过volumes配置挂载目录,此处我将本地的data/sentry目录与容器中的/var/lib/sentry/files目录映射起来。所以在本地的data/sentry目录中放入的文件,可以在容器的/var/lib/sentry/files目录中读取到。
- 在容器内部,配置文件存放在/etc/sentry目录下,执行拷贝命令
- 重启容器
使用sentry
基本使用
- 创建项目,输入项目名称,获取react对应的配置
- 到项目中进行配置(先安装依赖yarn add raven-js)
- 至此就可以在sentry服务端监听错误了,如下图所示
- 将source-map打包到js文件的同级目录下,sentry就会自动获取map文件,进行对应的源码解析。但上图中可以看到,并没有对应的源码,查了半天才发现自己是在localhost下测试的,服务端当然获取不到这个地址。将代码部署上线上后,可以顺利获取源码。
现在总是万事俱备只欠东风了吧,嘿嘿嘿,被邮件轰炸这种好事,肯定是独乐乐不如众乐乐了。团队建起来,成员加起来!
团队管理
- 新建团队
- 邀请新成员
- 接受邀请
4. 加入了团队
有了人,就可以分配bug啦,开不开心~
修改时区
收到的邮件中发现时间不对,需要自己设置正确的时区。
点击左下角的图标,选择账户(Account),在外观(Appearance)中修改时区。
项目实践
一般demo都是入门级的,在实际的项目中会遇到各种各样的问题。所以此处应该是不断更新的~
在本地测试dcoos-web项目的时候,刚好遇到两个错,一个是接口报错500 (Internal Server Error),另一个是Uncaught (in promise) TypeError: Cannot read property 'detail' of null,想着错误机会难得(QA在一边笑笑不说话),赶紧接入sentry体验一把。
结果我睁大眼睛看了半天,服务端一个错误都没有抓到。我怀着矛盾的心情手动添加了一个错误,既希望sentry不要抓到(那就说明我是个糊涂蛋,哪里配置写错了),又希望sentry能抓到错误(嗯,一击破坏了我对sentry的信仰),一刷新页面,果然抓到了错误,我的内心是崩溃的,我自己写的bug你才能抓到,我要你何用(下图的情况除外)!
一头扎入茫茫文档,列出了目前的两个问题:
Q1:怎么捕获指定状态的接口错误?
Q2:怎么捕获js中的异步错误?
Q1:怎么捕获指定状态的接口错误?
这个问题比较好解决,在接口返回的时候加一层过滤,不符合指定条件的请求throw Error,并把catch到的Error抛给sentry。
这样做的好处是统一处理,坏处是sentry报错会定位到这个处理函数本身,不是很直观。需要继续思考是否有更好的方式。
Q2: 怎么捕获js中的异步错误?
https://www.themarketingtechnologist.co/log-react-errors-to-sentry/
https://forum.sentry.io/t/asynchronous-error-capture-in-sentry-javascript/1503/4
https://github.com/getsentry/raven-js/issues/67
先上几个文献,意思大概是sentry不会自动捕获异步的错误。主要解决方案就是用全局捕获错误的方式。
官方针对promise的解决方案如下:
加了这行代码后,可以顺利抓到异步的错误了。
不是最后的最后
路要一步步走,坑要一个个踩。续集丰富多彩,敬请期待!