Sentry Raven.js学习
最近看看要不要在网上学习下性能监测和告警的解决方案,加在项目里。已经调研了一下才发现,项目里已经用上Raven.js了。实际上,各大公司也都有自己的实现方式,除了sentry的Raven.js外,还有腾讯的badjs,淘宝的JSTracker,阿里巴巴的FdSafe,支付宝的saijs等。早在几年前,就已经有许多解决方案了。
异常监测和信息采集的需要实现的主要功能点包括:
- 前端SDK实现包括错误拦截和监控,错误信息包装、信息上报、API设计等
- 提供一个可视化的管理后台
- 可以正确定位错误位置
- 可以对上报的日志进行筛选、查询、聚类等操作
- 可以用邮件、短信或集成在其他平台中通知开发者
从一个前端初学者的角度,下面更多聊一下前端SDK的细节。
前端SDK实现
前端实现上的技术重点有三:错误捕获和封装,AJAX上报和JSON字符串化参数。
在raven-js的vendor
目录下,引用json-stringify-safe
和Tracekit
。前者为了避免JSON.stringify
中出现的循环引用的情况,下面主要介绍后者。
Tracekit
常见的方案就是拦截window.onerror
方法,在做完自己的工作后,调用原来的window.onerror
。自己的工作里包括对错误信息的同一美化和包装。raven.js在这里是借助Tracekit.js完成的。
Tracekit主要分为两部分,Tracekit.report()
和Tracekit.computeStackTraceWrapper()
。前者主要用来绑定和解绑错误监听函数、拦截错误;后者主要用来格式化错误信息。
Tracekit.report()
在report()
里,整体的设计和基本的观察者设计模式一样,内部成员handlers
保存所有的事件消费者,与事件处理函数相关的有四个:
subscribe()
,绑定一个监听错误的函数,并在绑定第一个函数时替换原有的window.onerror
unsubscribe()
,解绑一个监听错误的函数,需要提供函数的引用unsubscribeAll()
,解绑所有监听错误的函数,还原原有的window.onerror
notifyHandlers()
,触发错误时,将处理过的错误分发给各handlers
1 | function notifyHandlers(stack, isWindowError) { |
另外,函数installGlobalHandler()
和uninstallGlobalHandler()
就是上文中用来拦截window.onerror
的函数。
1 | function installGlobalHandler() { |
report()
中最主要的函数是traceKitWindowOnError()
。它的工作流程如下:
- 查看lastException是否有正在处理的error,如果有则说明是当前错误引起的,使用
computeStackTrace.augmentStackTraceWithInitialElement()
追加到当前的错误栈前。调用processLastException()
,将lastException的信息交给handler处理,并将lastException置空。 - 如果lastException为空,且Error为错误对象,使用
computeStackTrace()
格式化错误信息,再交给错误消费者。 - 如果lastException为空,且Error不是错误对象(如字符串),则自行包装错误信息,交给消费者
- 使用原来的
window.onerror()
处理事件
1 | function traceKitWindowOnError(message, url, lineNo, colNo, ex) { |
Tracekit.computeStackTraceWrapper()
这一部分主要由下面几个函数组成:
computeStackTraceFromStackProp()
,处理Chrome和Gecko浏览器下的错误信息格式化computeStackTraceByWalkingCallerChain()
,处理IE和Safari浏览器下的错误信息格式化augmentStackTraceWithInitialElement()
,在当前错误栈底新增新的错误信息,用于computeStackTraceByWalkingCallerChain()
和第一部分的processLastException()
computeStackTrace()
,格式化错误栈信息
其中computeStackTraceFromStackProp()
通过换行符得到stack信息,并通过正则格式化所需要的错误信息,computeStackTraceByWalkingCallerChain()
是利用arguments.caller
得到错误栈信息并格式化。
computeStackTrace()
代码如下:
1 | function computeStackTrace(ex, depth) { |
除了Tracekit所做的工作外,raven本身也对console的log/warning/assert/error方法,setTimeout
,setInterval
,requestAnimationFrame()
以及各种事件handler进行了拦截。
这里有个坑,跨域的问题无法拦截错误,解决办法就是对跨域的script标签加入crossorigin属性,并在后台配置Access-Control-Allow-Origin=*
Raven
实际上,Tracekit本身已经完成对错误捕获和封装。Raven为了便于在管理后台展示和管理,进一步提出了DSN、context等设计。raven-js的源码主要在src/raven.js
中。剩下两部分也是在其中实现的。下面分部分介绍一些:
DSN
DSN(Data Source Name)是Sentry对一个项目的定义。它由协议、端口、用户、密码、后台Sentry服务器地址、项目名组成。通过Raven.config()
设置。在config()
中通过正则匹配用户输入的DSN字符串,得到后台地址。
1 | config: function(dsn, options) { |
安装和卸载
在install()
和uninstall()
函数中完成。
install()
中完成了下面的工作:
- 借助Tracekit监听了全局的错误事件
- 监听try catch和一些浏览器事件过程(如console,click,fetch等)中的信息
- 安装插件
1 | install: function() { |
uninstall
中还原了对浏览器原方法的修改,并卸载了Tracekit的report。
封装函数
相关函数:context()
和wrap()
。完成的主要工作是对浏览器原生方法的拦截,使得能更好地捕获其中的错误,在对象内部使用。
capture相关
用来捕获事件,有三种用法。
captureException()
,最典型的用法,借助Tracekit捕获页面的异常,之后进一步封装成frame后交给_send()
发送captureMessage()
,最常用的用法,类似埋点,将信息封装成frame后交给_send()
发送captureBreadcrumb
,类似captureMessage()
,不过储存信息在this._breadcrumbs
,并不交给_send()
1 | captureException: function(ex, options) { |
值得注意的是captureMessage
中可以设置rate,使一些消息不上报。白名单、正则过滤也是在这里完成的。captureException
则是在_processException
中完成的。
设置context
context包括三部分:
- tags,用于从不同维度标识错误或信息,使用
setTagsContext()
全局配置 - users,用于标识错误来源,使用
setUsersContext()
配置 - extra,用来携带额外的信息,这部分信息不会被索引,使用
setExtraContext()
配置
它们都放在Raven._globalContext
中。涉及的函数还有clearContext()
和getContext()
。
同时environment
和release
也放在Raven._globalContext
中,可以通过setEnvironment
和setRelease
设置
BreadCrumb
这部分功能是在_instrumentTryCatch
和_instrumentBreadcrumbs
方法里实现的。它们通过重写原方法,捕获其中的错误和事件。在卸载时,通过restoreBuiltin
还原。
发送
- 在
send()
方法中,会使用封装好的数据附加上_globalOptions
中的数据,附带浏览器的状态信息(_getHttpdata()
中实现)之后交由_sendProcessedPayload()
。 - 在
_sendProcessedPayload()
中,会裁剪过长的信息(message, stack, url, referer等)添加请求头,设置发送目标,传入成功和失败回调调用发送函数_makeRequest()
。 - 在
_makeRequest()
中,为了跨域发送,会优先尝试fetch,然后尝试带有withCredentials字段的XMLHttpRequest,最后采用XDomainRequest对象发送。
1 | _makeRequest: function(opts) { |
至此,错误捕获和封装,AJAX上报和JSON字符串化参数都已完成。
可视化后台
在自己设计异常监控系统时,需要和后台商量好接口的设定。用Express + React/Vue等方案快速搭建。