今天我们来聊聊前端的监控
我们为什么需要前端监控?
为了获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化方向
前端监控分为三类
性能项目
数据监控
异常监控
性能监控
– 衡量前端的性能的指标是时间
那么如何监测时间呢,浏览器给我们提供了一个 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 传给后台
综上所诉 大致就是这样 这里只是简单的介绍了下 需要深入了解的小伙伴请自行寻找相关资料,比如火焰图之类的