今天我们来聊聊前端的监控
我们为什么需要前端监控 ?
为了获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化方向
前端监控分为三类
- 性能项目
- 数据监控
- 异常监控
性能监控
- 衡量前端的性能的指标是时间
那么如何监测时间呢, 浏览器给我们提供了一个 API
来看看里面都有什么吧它的属性 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.js
performance.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文件不能用此方法捕获详细 具体查看监测用户行为
主要方式是在document上监听事件 通过事件委托获取事件对象 封装info 传给后台
综上所诉 大致就是这样 这里只是简单的介绍了下 需要深入了解的小伙伴请自行寻找相关资料, 比如火焰图之类的