前端监控的那些事

17f076de7684541311f73d7550a579b6.png

DING~ 你的页面又产生了N个bug,想必每个前端er对于前端监控都不陌生,不论是第三方的监控平台,亦或是自己公司研发的监控系统,包括转转目前使用的sentry,都是为了能够对我们的生产环境的系统进行及时有效的追踪,产生问题能够第一时间响应和解决。所以本文将对前端监控的原理做一些分析。

首先,我们要想一下,前端监控都是监控哪些东西呢?
  1. 错误监控

  2. 性能监控

  3. 自定义埋点

其次,我们收集到这些数据之后如何上报呢?
  1. xhr、axios、fetch等

  2. Image、Gif等

然后,我们应该在什么时候上报这些数据呢?
  1. 立刻上报

  2. 浏览器空闲时上报(requestIdleCallback/setTimeout)

  3. 页面渲染完上报(window.onload)

  4. 页面关闭前上报(beforeunload)

最后,上报完成后干什么呢?

  当然是该报警报警,该展示展示啦。

具体请参考下图

b9c284a1bc1612730c47890f5f2e70cc.png

明白了整个监控的流程,接下来我们展开每一个步骤,来具体分析下如何实现。


数据收集

一、错误监控

1、 js报错

可以使用 window.onerror 来监听 js 错误。

// 监听js错误
window.onerror = function(message, source, lineno, colno, error) { 
  // 在此处把所有错误信息存入一个堆栈中
  ...
}

我们可以看到函数可以收集到错误字符串信息、发生错误的js文件,错误所在的行数、列数、和Error对象(里面会有调用堆栈信息等)。根据具体需求,我们可以选择需要的信息进行上报。

2、资源加载错误

当资源(如img或script)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。这些error事件不会向上冒泡到window,但可以在捕获阶段被捕获 因此如果要全局监听资源加载错误,需要在捕获阶段捕获事件,即addEventListener('error')

window.addEventListener('error',function(e){
    let target = e.target, // 当前dom节点
        tagName = target.tagName,
        // 失败的次数
        count = target.dataset.count,
        // 失败的资源链接
        url = target.src
},true)

3、Vue 错误

Vue的报错需要单独使用vue提供的api来捕获:Vue.config.errorHandler

Vue.config.errorHandler = function (err, vm, info) {
  #处理错误信息, 进行错误上报
  #err错误对象
  #vm Vue实例
  #`info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  #只在 2.2.0+ 可用
}

在这里可以收集到报错的具体行数和列数,那么在排查问题时,就可以借助sourcemap来直接还原代码位置。

4、http请求错误

目前我们常用的请求库:axiosfetch,如果使用axios,那只需要在请求拦截器以及响应拦截器进行处理上报即可。如果是fetch,则需要重写方法,进行代理,代码核心为使用apply重新执行原有方法,并且在执行原有方法之前进行监听操作,最终我们只需要关心三种错误事件:aborterrortimeout

二、性能监控

首先为什么要对前端页面的性能做监控呢?你可以想象一下,当你中意的某个商品降价了,但要限时抢购,而你卡着时间点进了抢购页面,却加载了大半天,等页面出来的时候,可能抢购已经结束了。所以页面的性能,对于用户体验有着最直接的影响,尤其近几年单页面应用的盛行,这个影响显得更加突出。而衡量一个页面的性能,主要有以下几个节点:

  • Time to First Byte(TTFB):浏览器从请求页面开始到接收第一字节的时间,这个时间段内包括 DNS 查找、TCP 连接和 SSL 连接。

  • DomContentLoaded(DCL):事件触发的时间。当 HTML 文档被完全加载和解析完成之后

  • Load(L):onLoad 事件触发的时间。页面所有资源都加载完毕后(比如图片,CSS),onLoad 事件才被触发。

通俗来讲,也可以理解为页面的白屏时间以及首屏时间:

  • 白屏时间:用户看到页面展示出现一个元素的时间。很多人认为白屏时间是页面返回的首字节时间,但这样其实并不精确,因为头部资源还没加载完毕,页面也是白屏。

  • 首屏时间:页面第一屏所有资源完整展示的时间

那如何获取这些时间节点呢?恰好W3C帮我们引入了 Navigation Timing API,我们可以通过 window.performance 来计算所需的性能耗时。

下图是W3C的 Navigation Timing 的处理模型。从当前浏览器窗口卸载旧页面开始,到新页面加载完成,整个过程一共被切分为 9 个小块:提示卸载旧文档、重定向/卸载、应用缓存、DNS 解析、TCP 握手、HTTP 请求处理、HTTP 响应处理、DOM 处理、文档装载完成。每个小块的首尾、中间做事件分界,取 Unix 时间戳,两两事件之间计算时间差,从而获取中间过程的耗时(精确到毫秒级别)。

55bfcc1c41dc0afbd9e4cdfd0a4d90ce.png

使用 window.performance.timing 我们即可获取上述所有事件的耗时。以下为相关属性的定义:

.navigationStart 准备加载页面的起始时间
 .unloadEventStart 如果前一个文档和当前文档同源,返回前一个文档开始unload的时间
 .unloadEventEnd 如果前一个文档和当前文档同源,返回前一个文档开始unload结束的时间
 .redirectStart   如果有重定向,这里是重定向开始的时间.
 .redirectEnd     如果有重定向,这里是重定向结束的时间.
 .fetchStart        开始检查缓存或开始获取资源的时间
 .domainLookupStart   开始进行dns查询的时间
 .domainLookupEnd     dns查询结束的时间
 .connectStart                  开始建立连接请求资源的时间
 .connectEnd                     建立连接成功的时间.
 .secureConnectionStart      如果是https请求.返回ssl握手的时间
 .requestStart                     开始请求文档时间(包括从服务器,本地缓存请求)
 .responseStart                   接收到第一个字节的时间
 .responseEnd                      接收到最后一个字节的时间.
 .domLoading                       ‘current document readiness’ 设置为 loading的时间 (这个时候还未开始解析文档)
 .domInteractive               文档解析结束的时间
 .domContentLoadedEventStart    DOMContentLoaded事件开始的时间
 .domContentLoadedEventEnd      DOMContentLoaded事件结束的时间
 .domComplete        current document readiness被设置 complete的时间
 .loadEventStart      触发onload事件的时间
 .loadEventEnd       onload事件结束的时间

有了相关的时间节点,我们即可通过计算各时间节点的差值,来获取最终需要的性能指标:

const t = window.performance.timing
//重定向时间
const redirectTime = t.redirectEnd - t.redirectStart;

//dns查询耗时
const dnsTime = t.domainLookupEnd - t.domainLookupStart;

//TTFB 读取页面第一个字节的时间
const ttfbTime = t.responseStart - t.navigationStart;

//DNS 缓存时间
const appcacheTime = t.domainLookupStart - t.fetchStart;

//卸载页面的时间
const unloadTime = t.unloadEventEnd - t.unloadEventStart;

//tcp连接耗时
const tcpTime = t.connectEnd - t.connectStart;

//request请求耗时
const reqTime = t.responseEnd - t.responseStart;

//解析dom树耗时
const analysisTime = t.domComplete - t.domInteractive;

//白屏时间 
const blankTime = (t.domInteractive || t.domLoading) - t.fetchStart;

//domReadyTime
const domReadyTime = t.domContentLoadedEventEnd - t.fetchStart;

三、自定义埋点

除了对错误和性能的监控,我们的业务方也需要对用户的行为进行采集,以此针对用户的喜好及各种行为来对业务进行优化。

PV、UV

PV(page view) 是页面浏览量,UV(Unique visitor)用户访问量。PV 只要访问一次页面就算一次,UV 同一天内多次访问只算一次。

用户点击事件

可以通过 addEventListener 来监听鼠标事件,touchstart ,即可收集到用户每一次的点击,以及点击的坐标和元素等信息。

页面停留时长

用户进入页面记录一个初始时间,用户离开页面时用当前时间减去初始时间,就是用户停留时长。这个计算逻辑可以放在 beforeunload 事件里做。


上报方式

  1. xhr/axios/fetch:这几种方式需要考虑跨域、对页面性能的消耗等问题,一般较少采用这种方式进行上报。

  2. image、gif:不存在AJAX跨域问题,可做跨源的请求,同时没有浏览器兼容性问题。

  3. navigator.sendBeacon:目前大部分现代浏览器都支持 navigator.sendBeacon方法。这个方法可以用来发送一些统计和诊断的小量数据,特别适合上报统计的场景。使用 sendBeacon() 方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载。

综合以上几种方式的优劣,可以采用如下方案:

当浏览器支持sendBeacon方法,优先使用该方法,使用img方式降级上报。


上报时机

作为一个合格的监控sdk,它的第一前提是不能影响页面的性能。即当页面所有资源、dom树全部渲染完成后,再上报才是稳妥的方式。所以可以先把数据采集到,存入临时临时的堆栈中,当采集到一定量的数据后,再利用 requestIdleCallback,或者 window.onload 后再上报,而如果要计算用户的页面停留时长,则放入 beforeunload中上报。


以上为整个前端监控的流程,最终数据采集、上报完成后,则可以根据业务需要,开发数据聚合平台,来对页面进行可视化监控。而对于一些比较严重的报错,则可以设置阈值来进行邮件或者钉钉报警。当然,这部分又可以另开一篇进行讨论了。本篇暂不展开。

ebd8ab3e8879beaae28720e7796d6fd5.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值