# 性能与错误监控方案

# 背景

由于许多前端老项目需要性能优化,但是我们看不到用户实际数据,需要添加性能监控获取用户页面性能数据。同时许多项目需要用户页面错误数据,避免被动等待用户告诉我们bug。

# 方案

前端监控流程:数据采集 -> 数据上报 -> 服务端处理 -> 数据可视化

# 1. 性能数据采集

(1)页面信息采集。 window.performance.timing对象里包含了页面加载各个阶段的起始及结束时间。我们可以获取我们关心的几个属性:

// 白屏时间

whiteScreen: timing.domLoading - timing.navigationStart,

// 重定向耗时

redirect: timing.redirectEnd - timing.redirectStart,

// DOM 渲染耗时

dom: timing.domComplete - timing.domLoading,

// 页面加载耗时

load: timing.loadEventEnd - timing.navigationStart,

// 页面卸载耗时

unload: timing.unloadEventEnd - timing.unloadEventStart,

// 请求耗时

request: timing.responseEnd - timing.requestStart,

// 获取性能信息时当前时间

time: new Date().getTime(),

真正的白屏时间需要根据各自项目自定义计算,比如:从用户打开页面到首页接口返回数据并展示时间。

(2)资源加载信息采集。 通过window.performance.getEntriesByType('resource')方法可以获取页面当前加载的所有资源信息。它一般包括以下几个类型:script/link/img/css/fetch/other/xmlhttprequest,我们需要使用的信息有:

// 资源的名称

name: item.name,

// 资源加载耗时

duration: item.duration.toFixed(2),

// 资源大小

size: item.transferSize,

// 资源所用协议

protocol: item.nextHopProtocol,

# 2. 错误数据采集

页面经常捕捉的错误有6种:

  1. 资源加载错误。通过addEventListener('error', callback, true)进行捕捉。
  2. js执行错误。通过window.onerror捕捉js错误。
  3. promise错误。通过addEventListener('unhandledrejection', callback)捕捉promise错误。
  4. console.error错误。
  5. window.fetch请求错误。
  6. window.XMLHttpRequest请求错误。

还有vue框架里的错误Vue.config.errorHandler,需要自定义方法进行采集。

# 3. 性能数据上报

性能数据可以在页面加载完之后上报,不能对页面性能造成影响,可以在浏览器空闲时间进行采集并上报。这个功能不是所有浏览器都支持,需要做兼容判断。例如:

window.onload = () => {
    if (window.requestIdleCallback) {
        window.requestIdleCallback(() => {
            report.performance = getPerformance()
            report.resources = getResources()
reprotData()
        })
    } else {
        setTimeout(() => {
            report.performance = getPerformance()
            report.resources = getResources()
reprotData()
        }, 0)
    }
}

# 4. 错误数据上报

错误数据上报跟性能数据上报为什么分开上报?性能数据跟错误数据不同,性能数据在页面加载完成时所有的数据已拿到手,而错误数据,是会在整个页面周期不断的收集数据。一个是一次性数据,一个是源源不断的数据。错误上报时机一般有延时上报和及时上报,但是用户一旦关系了浏览器传统处理方法就会上报失败。浏览器引入了navigator.sendBeacon方法。这个方法还是异步发出请求,但是请求与当前页面脱钩,作为浏览器的任务,因此可以保证会把数据发出去,不拖延卸载流程。

navigator.sendBeacon方法有以下特点:

(1)发出的是异步请求,并且是POST请求,后端解析参数时,需要注意处理方式;

(2)发出的请求,是放到的浏览器任务队列执行的,脱离了当前页面,所以不会阻塞当前页面的卸载和后面页面的加载过程,用户体验较好;

(3)只能判断出是否放入浏览器任务队列,不能判断是否发送成功;

(4)Beacon API不提供相应的回调,因此后端返回最好省略response body。

navigator.sendBeacon方法接受两个参数,第一个参数是目标服务器的 URL,第二个参数是所要发送的数据(可选),可以是任意类型(字符串、表单对象、二进制对象等)。使用方法:

window.unload = function() { // 在页面卸载的时候上报错误数据
  navigator.sendBeacon('/xxxx', report.errors)
}