聊一聊前端的监控

今天我们来聊聊前端的监控
我们为什么需要前端监控 ?
为了获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化方向
前端监控分为三类

性能项目
数据监控
异常监控

性能监控
– 衡量前端的性能的指标是时间
那么如何监测时间呢, 浏览器给我们提供了一个 API performance来看看里面都有什么吧
它的属性 timing 是一个PerformanceTiming 对象 , 包含了延迟相关的性能,timing里的属性基本都是成双成对的 都是以xxxstart — xxxend end跟start的时间差就是我们所需要的信息
那么我们来捋一捋网页打开到关闭 都有哪些时间戳记录下来,看一张网页加载流程图
首先是navigationStart 就是地址栏开始加载 期间会执行unload 卸载上一个网页的内容 如果有重定向(同域名),就开始重定向。
fetchStart 抓取开始 页面开始加载 domainLookupStart dns 解析开始domainLookupEnd dns 解析结束connectStart tcp 握手开始connectEnd 建立连接requestStart 请求页面开始responseStart 响应开始responseEnd 响应结束domloading dom开始加载domInteractive dom结构树加载完成(src外链资源未完成)domcontentLoaded($(function(){}))domComplete dom 加载完毕 外链资源加载完毕loadevent (window.onload=function(){} 里面的代码加载完毕)
监控页面加载时间
那么我们开始写个监控吧 先起一个服务

serve.js
let Koa = require(‘koa’)
let Server = require(‘koa-static’)
let path = require(‘path’)
let app = new Koa()

app.use(Server(path.resolve(__dirname)))

app.listen(3000,function(){
console.log(‘Server is running on port 3000’)
})
将serve作为静态资源目录 服务起在端口3000
再新建performance.jsperformance.js
// 性能监控
let time = ()=>{
let timing = performance.timing
let data = {
prevPage: timing.fetchStart – timing.navigationStart , // 上一个页面卸载到新页面的时间
redirect: timing.redirectEnd – timing.redirectStart , // 重定向时长
dns: timing.domainLookupEnd – timing.domainLookupStart, // dns 解析时长
tcp: timing.connectEnd – timing.connectStart ,// tcp 链接时长
respone: timing.responseEnd – timing.requestStart, // 响应时长
ttfb: timing.responseStart – timing.navigationStart, // 首字节接收到 的时长
domReady:timing.domInteractive – timing.domLoading, // dom 准备时长
whiteScreen:timing.domLoading – timing.navigationStart, // 白屏时间
dom:timing.domComplete – timing.domLoading, // dom解析时间
load:timing.loadEventEnd – timing.loadEventStart,
total:timing.loadEventEnd – timing.navigationStart
}
return data
}

// 因为检测代码在load里执行 所以此时load事件未完成 检测不到load的时间 所以我们需要设置一个定时器来监听load完成

let timeout = (cb)=>{
let timer;
let check = ()=>{
// 加载完毕后才有performance.timing.loadEventEnd
if(performance.timing.loadEventEnd){
timer = null
cb()
}else{
timer = setTimeout(check,100)
}
}
window.addEventListener(‘load’,check,false)
}

let domReady = (cb)=>{
let timer;
let check = ()=>{
// performance.timing.domInteractive
if(performance.timing.domInteractive){
timer = null
cb()
}else{
timer = setTimeout(check,100)
}
}
window.addEventListener(‘DOMContentLoaded’,check,false)
}

let _performance = {
init(cb){
//有可能domloaded 并未加载好 用户就关闭网页了 这种情况也希望监听到
domReady(()=>{
let data = time()
data.type = ‘domReady’
cb(data)
})
// dom完全加载完毕
timeout(()=>{
let data = time()
data.type = ‘loaded’
cb(data)
})
}
}

// 通过_performance.init(cb) 获取到监控数据

_performance.init((data)=>{console.log(data)})
html 引入performance.js
运行结果为:
然后发送图片到服务器

// 通过_performance.init(cb) 获取到监控数据

let formatter = (data)=>{
let arr = []
for(key in data){
arr.push(`${key}=${data[key]}`)
}

return arr.join(‘&’)

}
_performance.init((data)=>{
// 然后我们创建一张空图片把数据发给服务器

let img = new Image()
img.src = ‘/p.gif?’ + formatter(data)
})

监控页面资源加载情况
想要监控资源就得获取到__proto__ 上的getEntriesByType(‘resource’)方法获取到的是个数组 包含了资源请求的时间 名字type 之类的 我们所做的就是拿区我们自己需要的东西
performance.js
//代码省略
let resource = performance.getEntriesByType(‘resource’)
let data = resource.map(_=>{
return {
name:_.name,
initatorType:_.initiatorType,
duration:_.duration
}
})
cb(data)
ajax请求监控
ajax 请求监控就简单了performance.js
// 为了获取到我们所需要的参数 我们改写一下 XMLHttpRequest.prototype.open(method,url,isAsync)

let ajax = {
init(cb){
let xhr = window.XMLHttpRequest
// 保存原来的open方法
let oldOpen = xhr.prototype.open
console.log(oldOpen)
xhr.prototype.open = function(method,url,isAsync = true,…args){

this.info = {method,url,isAsync,message:args}

return oldOpen.apply(this,arguments)
}
let oldSend = xhr.prototype.send
xhr.prototype.send = function(value){
let start = Date.now()
let fn = (type) => () =>{
this.info.time = Date.now() – start
this.info.requestSize = value ? value.length : 0
this.info.responeSize = this.responseText.length
this.info.type = type
cb(this.info)
}
this.addEventListener(‘load’,fn(‘success’),false)
this.addEventListener(‘error’,fn(‘error’),false)
this.addEventListener(‘abort’,fn(‘abort’),false)
return oldSend.apply(this,arguments)
}
}
}
ajax.init((data)=>{
console.log(data)
})
index.html
// ajax 请求监控 原生ajax

var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象

request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// 成功,通过responseText拿到响应的文本:

} else {
// 失败,根据响应码判断失败原因:

}
} else {
// HTTP请求还在继续…
}
}

// 发送请求:
request.open(‘GET’, ‘/api/categories’,true,’fet’);
request.send();
结果如下
错误文件的捕获
主要是通过window.onerror 事件来捕获
let errorCatch = {
init(cb){
window.addEventListener(‘error’,function(message, source, lineno, colno, error) {
this.info = {}
let stack = error.stack;
let matchUrl = stack.match(/http:\/\/[^\n]*/)[0] // 获取报错路径

this.info.filename = matchUrl.match(/http:\/\/(?:\S*)\.js/) ? matchUrl.match(/http:\/\/(?:\S*)\.js/)[0]:’html’ // 获取报错文件;
let [,row,col] = stack.match(/:(\d+):(\d+)/) // 获取报错行 列
this.info = {
message:error.message,
name:error.name,
filename:this.info.filename, //有可能是html里的js报错
row,
col, // 真实上线的时候 需要source-map 去找到真正的行跟列
}
cb(this.info)
},true)
}
}
errorCatch.init(data=>{console.dir(data)})

需要值得注意的是promise 无法用此方法捕获!! 需要另行监听另外的事件另外不同域的js文件不能用此方法捕获详细 具体查看https://www.jianshu.com/p/315…
监测用户行为
主要方式是在document上监听事件 通过事件委托获取事件对象 封装info 传给后台

综上所诉 大致就是这样 这里只是简单的介绍了下 需要深入了解的小伙伴请自行寻找相关资料, 比如火焰图之类的

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理