前端监控方案探究

最近有在总结一些前端监控的方案,这篇文章用来做一个汇总,篇幅会以方案描述和方法论为主,也会有部分代码。

性能监控

从客户输入网址(或者打开 webview 页面)到渲染完成,加载的时间是一个决定客户体验的关键指标,移动端 webview 可以使用一些预加载,离线化的方式来节约一些网络请求和渲染的时间(细节不属于这篇文章的范围)。但如果是在线加载的话,则一些指标则变得尤为重要:

-- DNS 查找时间
-- TCP 建立连接时间
-- 请求页面时间
-- load 事件花费的时间
-- 前端消耗的总时间
-- 白(首)屏时间
-- 页面完全加载总时间
-- DOM ready 时间
-- 可操作时间
-- 首字节时间 (ttfb)

如何获取这些时间,则需要使用 Performance.timing 或者 Performance Timeline, 两者同属于 Performance API,Timeline 时间上更新一些,与之前的 resource 类似,使用 getEntries 来获取到一些 performance 的时间节点。不过这篇文章后面的代码还是通过 .timing 来获取时间节点的,两者属性类似,切换也并不困难。

下面我们来看下 window.performance.timing 到底记录了哪些数据:

上图包含了我输入博客网址 rannie.bitcron.com 并加载后,每一个节点的时间戳,属性的具体含义可以通过 MDN PerformanceTiming 文档来了解。

下图能更直观的看到属性与加载 pipeline 各个阶段的对应关系:

下面我们则可以通过时间取差值的方式来获取一些关键指标,如图所示:

扩展开来,我们来获取上面一开始描述过的指标:

这里的 timeFunc 其实就是对一些边界场景做了处理,比如属性为负值,结果为负等可能会干扰到我们分析数据的情况。另外 蚂蚁金服如何把前端性能监控做到极致? 里也提到了一些上面我们遗漏的场景,比如说 DNS 查询完,马上就开始建立 TCP 连接了吗?TCP 连接完马上就开始去发送这个请求了吗?实际上是不一定的,除了我们刚才模型定义的阶段之外,会有一个叫做 stalled 的时间,那么这个阶段发生了什么事情呢?

当你观察到有这种 blocked 或者 stalled 的时间出现的时候,大部分情况下,都是因为被同域名 TCP 并发连接限制了,就比如说,我们一个页面可能加载了 CDN 上的十个资源,那么在 TCP 连接会有限制,比如 Chrome 它就会有限制。单页面、单个域名的 TCP 连接,并发数是 6,也就是说加载十个资源,只有六个连接被真正建立了,剩下四个资源是要等待这六个连接消费完成之后才能被复用的。

还有其他的一些情况也会导致这个页面 stalled 的时间产生,如果我们只是用刚才那个性能去计算某一个阶段的时间的话,就会把这个 stalled 的时间漏掉,这样会导致我们真正把各个阶段加起来去算这个页面总的加载时间的时候,出现一个误差。在实际观察中我们会发现,这种 stalled 时间是非常常见的。

另外,如果我刷新一下博客再获取一下时间,

可以看到缓存会帮我们跳过 DNS 以及 TCP 建立连接阶段,直接开始请求,所以在分析数据时,值为零的情况也要考虑到或者直接在前端将其清洗掉。

另外,通过 performance.timing 获取时间是,在某个时机获取的数据可能并不稳定(可能为 0 或者负值),可以启动一个 setTimeout 再来获取,如果我们使用 Performance Timeline API 来获取,则可以避免这个问题。

异常监控

通常我们对前端的各种产品也要进行异常监控,比如 Script Error, 比如 App 的 crash,那么对于 web 来说,通常会有哪些错误以及是如何引起的。

一般我们会通过 hook window.onerror 方法来全局捕获错误信息:

如图所示,拿到 errorObj 后我们会对他进行简单的解构和格式化,然后再记录或上报,我们需要获取到报错的具体位置(row & column),然后配合 source map 来还原。另外还需要拿到报错的执行堆栈,错误类型等待,来帮助我们具体分析这个错误是如何产生的以及如何修复。

使用 window.onerror 时,还需要注意一点就是跨域的问题,如果是监控的 js 文件跟发生错误的文件不在一个 domain 下,则不会拿到任何有用的错误信息,所以需要对非同域的 js 文件加 Access-Control-Allow-Origin:*, 在 script 标签里也要标注 crossorigin

关于 errorObj ,其实也不是在所有的浏览器下都能拿到,我们可以看下获取信息的兼容性:

关于 React 以及 Vue 这样的框架,其实有自带的错误采集接口的,Vue 的话有 Vue.config.errorHandler
React 则可以通过一个 ErrorBoundary 的组件来包裹我们的业务组件即可:

网络监控

网络监控如果是使用 axios 可以直接通过 interceptor 来拦截,获取请求/响应状态, 一般来说只需要上报那些报错的接口即可:

如果是监控 ajax ,则可以 hook XMLHttpRequest 的 open 以及 send 方法:

行为监控

有的时候需要我们记录用户的行为来辅助进行错误以及业务分析,获取用户行为一般可以通过全局监听 click 事件,然后获取到点击的元素来记录用户点击的事件顺序,大概的逻辑如下:

最后,再提一下监控上报。。。

一般我们可以通过 GET 请求一个 image 的方式,把 log 信息拼接到 url 上,但有些日志如果文件较大,则可以使用单独的 POST 方式发送到后端,后端存储到 DB 以方便将来给监控平台使用这些数据。

-- EOF --
以上为本篇博客的全部内容,欢迎提出建议和指正,
个人联系方式详见关于

Comments
Write a Comment