前端监控一般包括:1.性能监控;2.错误监控
1.前端性能
这里引用他人文章对前端性能的描述:通常前端性能可以认为是用户获取所需要页面数据或执行某个页面动作的一个实时性指标,一般以用户希望获取数据的操作到用户实际获得数据的时间间隔来衡量。
简单的总结一下,就是一般用时间来衡量前端性能。比如一些耳熟能详的:白屏时间、首屏时间等
那么当我们打开一个网页,如果获取跟网页性能相关的时间呢?
2.性能数据采集
打开控制台,输入window.performance可以看到performance对象的组成,其中我们关注一个比较有意思的属性timing,都是一些页面加载关键节点的时间戳。通过这些时间戳的加加减减,我们就能得到某个节点所耗费的时间。
通过window.performance.timing提供的各个时间节点时间戳相减可以得到白屏时间、dom渲染耗时、重定向耗时、页面加载耗时等页面性能数据。那首屏时间如何计算呢?首屏时间的计算比较复杂,因为涉及图片等多种元素及异步渲染等方式,在这里先不写,后续有时间再研究。
window.performance.timing已经得到页面性能的相关耗时,那页面资源的加载耗时呢?
通过控制台可以看到页面每个资源的耗时,那么通过js如何获取呢?
通过window.performance.getEntriesByType(‘resource’)可以获得页面每个资源的加载详情。如下,在控制台打印一下:
具体资源详情如下图
前端错误又分为:
1.资源加载错误;可利用addEventListener(‘error’, callback, true)采集
2.js执行错误;可利用window.onerror采集
3.promise错误;可利用addEventListener(‘unhandledrejection’, callback)采集
const errors = []
function collectError() {
// 资源加载错误数据采集
addEventListener('error', e => {
const target = e.target
if(target != window) {
errors.push({
type: target.localName,
url: target.src || target.href,
msg: (target.src || target.href) + ' is load error',
time: new Date().getTime(), // 错误发生的时间
})
}
}, true)
// 监听js错误
window.onerror = function(msg, url, row, col, error) {
errors.push({
type: 'javascript',
row: row,
col: col,
msg: error && error.stack? error.stack : msg,
url: url,
time: new Date().getTime(), // 错误发生的时间
})
}
// 监听 promise 错误 缺点是获取不到行数数据
addEventListener('unhandledrejection', e => {
errors.push({
type: 'promise',
msg: (e.reason && e.reason.msg) || e.reason || '',
time: new Date().getTime(), // 错误发生的时间
})
})
}
collectError()
4.数据上报
收集到页面的性能数据、错误数据,应该选择什么时间上报给后台呢?
4.1 性能数据上报
性能数据可以在页面加载完成之后采集并上报,尽量不要对页面性能造成影响。
这里提到一个有意思的函数requestIdleCallback:window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。
利用浏览器空闲函数可以做页面性能的采集和上报,这样不影响页面的正常性能。当然,这个功能不是所有浏览器都支持,需要做兼容判断。
const monitor = { // 前端监控
performance: null, // 性能
resources: [], // 资源
errors: [] // 错误
}
window.onload = function() {
if(window.requestIdleCallback) { // 如果浏览器支持这个方法,利用这个方法采集页面性能数据
window.requestIdleCallback(() => {
monitor.performance = getPerformance()
monitor.resources = getResources()
uploadMonitor()
})
} else {
setTimeout(function() {
monitor.performance = getPerformance()
monitor.resources = getResources()
uploadMonitor()
}, 0)
}
}
function uploadMonitor() { // 上报前端监控的性能数据+资源数据
axios.post('/xxxx', { performance: monitor.performance, resources: monitor.resources } )
// 上报成功的话,可以把数据 monitor.performance 和 monitor.resources 清一下
}
4.2 错误数据上报
-
用一个数组收集所有错误,再统一在某个阶段上报(延时上报)
-
在错误发生时上报(即时上报)。这样可以避免“收集完错误,但延时上报还没触发,用户却已经关掉网页导致错误数据丢失”的问题。
-
可以使用 navigator.sendBeacon() 来进行上报。
在这里cue一下,错误数据上报跟性能数据上报为什么建议分开上报?性能数据跟错误数据不同,性能数据在页面加载完成时所有的数据已拿到手,而错误数据,是会在整个页面周期不断的收集数据。一个是一次性数据,一个是源源不断的数据。
window.unload = function() { // 在页面卸载的时候上报错误数据
navigator.sendBeacon('/xxxx', monitor.errors)
}
下面给出完整的前端监控代码
const monitor = { // 前端监控
performance: null, // 性能
resources: [], // 资源
errors: [] // 错误
}
// 获取页面加载的时间性能信息
function getPerformance() {
if (!window.performance) return
const timing = window.performance.timing
return {
whiteScreen: timing.domLoading - timing.navigationStart, // 白屏时间
redirect: timing.redirectEnd - timing.redirectStart, // 重定向耗时
dom: timing.domComplete - timing.domLoading, // dom渲染耗时
load: timing.loadEventEnd - timing.navigationStart, // 页面加载耗时
unload: timing.unloadEventEnd - timing.unloadEventStart, // 页面卸载耗时
request: timing.responseEnd - timing.requestStart, // 请求耗时
time: new Date().getTime(), // 获取性能信息时当前时间
}
}
// 获取页面资源加载的时间性能信息
function getResources() {
if (!window.performance) return
const data = window.performance.getEntriesByType('resource')
const resources = {
css: [],
img: [],
script: [],
xmlhttprequest: [],
link: [],
fetch: [],
iframe: [],
other: [],
time: new Date().getTime() // 获取资源信息时当前时间
}
data.forEach(item => {
let key = resources[item.initiatorType]?item.initiatorType:'other'
resources[key].push({
name: item.name, // 资源的名称
duration: item.duration.toFixed(2), // 资源加载耗时
size: item.transferSize, // 资源大小
protocol: item.nextHopProtocol, // 资源所用协议
})
})
return resources
}
// 采集页面错误
function collectError() {
// 资源加载错误数据采集
addEventListener('error', e => {
const target = e.target
if(target != window) {
monitor.errors.push({
type: target.localName,
url: target.src || target.href,
msg: (target.src || target.href) + ' is load error',
time: new Date().getTime(), // 错误发生的时间
})
}
}, true)
// 监听js错误
window.onerror = function(msg, url, row, col, error) {
monitor.errors.push({
type: 'javascript',
row: row,
col: col,
msg: error && error.stack? error.stack : msg,
url: url,
time: new Date().getTime(), // 错误发生的时间
})
}
// 监听 promise 错误 缺点是获取不到行数数据
addEventListener('unhandledrejection', e => {
monitor.errors.push({
type: 'promise',
msg: (e.reason && e.reason.msg) || e.reason || '',
time: new Date().getTime(), // 错误发生的时间
})
})
}
function uploadMonitor() { // 上报前端监控的性能数据+资源数据
axios.post('/xxxx', { performance: monitor.performance, resources: monitor.resources } )
}
function uploadMonitorErrors() { // 上报前端监控的错误数据
navigator.sendBeacon('/xxxx', monitor.errors)
}
window.onload = function() { // 在页面加载完后上报性能数据
if(window.requestIdleCallback) { // 如果浏览器支持这个方法,利用这个方法采集页面性能数据
window.requestIdleCallback(() => {
monitor.performance = getPerformance()
monitor.resources = getResources()
uploadMonitor()
})
} else {
setTimeout(function() {
monitor.performance = getPerformance()
monitor.resources = getResources()
uploadMonitor()
}, 0)
}
}
window.unload = function() { // 在页面卸载的时候上报错误数据
uploadMonitorErrors()
}
collectError()