当前位置: 首页 > news >正文

聊一聊前端的监控

今天我们来聊聊前端的监控

我们为什么需要前端监控 ?

为了获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化方向

前端监控分为三类

  1. 性能项目
  2. 数据监控
  3. 异常监控

性能监控

- 衡量前端的性能的指标是时间

那么如何监测时间呢, 浏览器给我们提供了一个 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.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 事件来捕获
clipboard.png

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)})

clipboard.png

需要值得注意的是
promise 无法用此方法捕获!! 需要另行监听另外的事件
另外不同域的js文件不能用此方法捕获详细 具体查看https://www.jianshu.com/p/315...

监测用户行为

主要方式是在document上监听事件 通过事件委托获取事件对象 封装info 传给后台

clipboard.png

clipboard.png

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

相关文章:

  • android图片蒙层
  • Docker学习笔记_使用Dockerfile创建flask的一个镜像
  • java 多线程基础, 我觉得还是有必要看看的
  • Nginx压测和并发预估
  • 未发先侃?对比华为,高通第二代5G调制解调器如何?
  • 安卓P底部有空白,需要手动全屏
  • vue.js框架原理浅析
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • SpringBoot 实战 (九) | 整合 Mybatis
  • ThinkSNSPlus 2.0 产品体验报告
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 资源 | 上千份简历模板统统给你!都拿去!
  • idou老师教你学Istio :5分钟简析Istio异常检测
  • CSS 专业技巧
  • 交互设计原则
  • bearychat的java client
  • Django 博客开发教程 16 - 统计文章阅读量
  • Effective Java 笔记(一)
  • ES6简单总结(搭配简单的讲解和小案例)
  • ESLint简单操作
  • extjs4学习之配置
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JavaScript实现分页效果
  • magento2项目上线注意事项
  • Swoft 源码剖析 - 代码自动更新机制
  • 转载:[译] 内容加速黑科技趣谈
  • 阿里云服务器购买完整流程
  • 如何正确理解,内页权重高于首页?
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​Linux·i2c驱动架构​
  • #LLM入门|Prompt#3.3_存储_Memory
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • %@ page import=%的用法
  • (03)光刻——半导体电路的绘制
  • (1)SpringCloud 整合Python
  • (Java数据结构)ArrayList
  • (JS基础)String 类型
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (一) storm的集群安装与配置
  • (一)C语言之入门:使用Visual Studio Community 2022运行hello world
  • .gitignore文件---让git自动忽略指定文件
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .NET gRPC 和RESTful简单对比
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .net 写了一个支持重试、熔断和超时策略的 HttpClient 实例池
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .net下的富文本编辑器FCKeditor的配置方法
  • /var/spool/postfix/maildrop 下有大量文件
  • @html.ActionLink的几种参数格式
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • @四年级家长,这条香港优才计划+华侨生联考捷径,一定要看!