入门前端监控

news2024/12/21 16:33:22

背景

  • 前端监控是指通过一系列手段对Web页面或应用程序进行实时监控和数据采集,以了解页面或应用程序的性能状况、用户行为等等,并及时发现和解决潜在的问题。
  • 一个完整的前端监控平台可以包括:数据收集与上报、数据整理与存储、数据展示
  • 这里仅介绍第一个环节——数据收集与上报,该环节可以分为收集阶段和上报阶段,大致情况如下:
    请添加图片描述

请添加图片描述

1、错误数据收集

1.1、js 错误

  • js 错误类型有语法错误、同步错误、异步错误。语法错误在开发阶段就可被发现,不做考虑;同步错误可以被try catch给捕获到的,一般在 catch 语句中手动上报错误;
// 手动捕获错误函数
export function errorCaptcher(error, msg) {
  // 上报错误
  lazyReport('error', {
    message: msg,
    error: error,
    errorType: 'catchError'
  });
}
  • 异步错误无法被try catch捕获到的,可以使用window.onerror监听

    // 防止多次使用 onerror,覆盖的情况
    const originOnError = window.onerror;
    window.onerror = function (msg, url, row, col, error) {
     if (originOnError) {
       originOnError.call(window, msg, url, row, col, error);
     }
     // 错误上报
     lazyReport('error', {
       message: msg,
       file: url,
       row,
       col,
       error,
       errorType: 'jsError'
     });
    }
    

1.2、promise 错误

  • window.onerror对于promise错误是无能为力的,一般使用 addEventListener() 监听 unhandledrejection 事件,可以捕获到未处理的 promise 错误
  window.addEventListener('unhandledrejection', (error) => {
    lazyReport('error', {
      message: error.reason,
      error,
      errorType: 'promiseError'
    });
  });

1.3、资源加载错误

  • 使用 addEventListener() 监听 error 事件,可以捕获到资源加载失败错误
  // resource error 捕获
  window.addEventListener('error', (error) => {
    let target = error.target;
    let isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
    // js error不再处理,避免重复上报
    if (!isElementTarget) {
      return;
    }
    lazyReport('error', {
      message: "加载 " + target.tagName + " 资源错误",
      file: target.src,
      errorType: 'resourceError'
    });
  }, true)

2、行为数据收集

2.1、用户埋点统计

  • 埋点是监控用户在我们应用上的一些动作表现,埋点又分为手动埋点和无痕埋点
  • 手动埋点就是手动的在代码里面添加相关的埋点代码,比如用户点击某个按钮,就在这个按钮的点击事件中加入相关的埋点代码
<button
  onClick={() => {
    // 业务代码
  	tracker('click', '用户去支付');
  }}
>手动埋点</button>

// 向外暴露的手动上报函数
export function tracker(actionType, data) {
  lazyReport('action', {
    actionType,
    data
  });
}
  • 无痕埋点是为了解决手动埋点的缺点,实现一种不用侵入业务代码就能在应用中添加埋点监控的埋点方式
// 自动埋点实现
function autoTracker () {
  // 添加全局click监听
  document.body.addEventListener('click', function (e) {
    const clickedDom = e.target;
    // 获取data-target属性值
    let target = clickedDom?.getAttribute('data-target');
    if (target) {
      // 如果设置data-target属性就上报对应的值--手动埋点
      tracker('click', target);
    } else {
      // 如果没有设置data-target属性就上报被点击元素的html路径
      const path = getPathTo(clickedDom);
      tracker('click', path);
    }
  }, false);
};

2.2、PV统计

  • PV即页面浏览量,用来表示该页面的访问数量
  • 在SPA应用之前只需要监听 onload 事件即可统计页面的PV,在SPA应用中,页面路由的切换完全由前端实现,主流的react和vue框架都有自己的路由管理库,而单页路由又区分为 hash 路由和 history 路由,两种路由的原理又不一样,所以统计起来会有点复杂。这里将分别针对两种路由来实现不同的采集数据的方式

2.2.1、history 路由

  • history路由依赖全局对象 history 实现,常用有 go、back、forward、pushState 和 replaceState 五种方法
  • history路由的实现主要依赖的就是 pushStatereplaceState 来实现的,但是这两种方法不能被 popstate 监听到,所以需要对这两种方法进行重写来实现数据的采集
  • 下面的代码中提供了方法的重写,以及监听页面的跳转和停留时间
export function historyPageTrackerReport() {
  let beforeTime = Date.now(); // 进入页面的时间
  let beforePage = ''; // 上一个页面

  // 获取在某个页面的停留时间
  function getStayTime() {
    let curTime = Date.now();
    let stayTime = curTime - beforeTime;
    beforeTime = curTime;
    return stayTime;
  }

  // 重写方法
  const createHistoryEvent = function (name) {
    // 拿到原来的处理方法
    const origin = window.history[name];
    return function(event) {
      let res = origin.apply(this, arguments);
      let e = new Event(name);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return res;
    };
  };
  window.history.pushState = createHistoryEvent('pushState');
  window.history.replaceState = createHistoryEvent('replaceState');

  // history.pushState
  window.addEventListener('pushState', function () {
    listener()
  });
  // history.replaceState
  window.addEventListener('replaceState', function () {
    listener()
  });
  // 页面load监听
  window.addEventListener('load', function () {
    listener()
  });
  // unload监听
  window.addEventListener('unload', function () {
    listener()
  });
  // history.go()、history.back()、history.forward() 监听
  window.addEventListener('popstate', function () {
    listener()
  });

  function listener() {
    const stayTime = getStayTime(); // 停留时间
    const currentPage = window.location.href; // 页面路径
    lazyReport('visit', {
      stayTime,
      page: beforePage,
    })
    beforePage = currentPage;
  }
}

2.2.2、hash 路由

  • url 上 hash 的改变会出发 hashchange 的监听,所以只需要在全局加上一个监听函数,在监听函数中实现采集并上报。但是在react和vue中,对于 hash 路由的跳转并不是通过 hashchange 的监听实现的,而是通过 pushState 实现,所以,还需要加上对 pushState 的监听
export function hashPageTrackerReport() {
  let beforeTime = Date.now(); // 进入页面的时间
  let beforePage = ''; // 上一个页面

  function getStayTime() {
    let curTime = Date.now();
    let stayTime = curTime - beforeTime;
    beforeTime = curTime;
    return stayTime;
  }

  function listener() {
    const stayTime = getStayTime();
    const currentPage = window.location.href;
    lazyReport('visit', {
      stayTime,
      page: beforePage,
    })
    beforePage = currentPage;
  }

  const createHistoryEvent = function (name) {
    const origin = window.history[name];
    return function(event) {  
      let res = origin.apply(this, arguments);
      let e = new Event(name);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return res;
    };
  };

  window.history.pushState = createHistoryEvent('pushState');

  // history.pushState
  window.addEventListener('pushState', function () {
    listener()
  });
  // hash路由监听
  window.addEventListener('hashchange', function () {
    listener()
  });
  // 页面load监听
  window.addEventListener('load', function () {
    listener()
  });
}

2.3、UV统计

  • UV统计的是一天内访问该网站的用户数
  • uv统计比较简单,就只需要在SDK初始化的时候上报一条消息就可以了
function init(options) {
  // 拿到配置信息 注入监控代码
  loadConfig(options);

  // uv统计
  lazyReport('user', '加载应用');
}

3、性能数据采集

3.1、FCP 统计

  • FCP(first-contentful-paint),从页面加载开始到页面内容的任何部分在屏幕上完成渲染的时间
  • 性能指标都需要通过 PerformanceObserver 来获取,它是一个性能监测对象,用于监测性能度量事件
export function observePaint() {
    if (!window.PerformanceObserver) return
    
    const entryHandler = (list) => {        
        for (const entry of list.getEntries()) {
            if (entry.name === 'first-contentful-paint') {
                observer.disconnect()
            }
    
            const json = entry.toJSON()
            delete json.duration
    
            const reportData = {
                ...json,
                subType: entry.name,
                pageURL: window.location.href,
            }

            lazyReport('performance-fcp', reportData)
        }
    }
    
    const observer = new PerformanceObserver(entryHandler)
    observer.observe({ type: 'paint', buffered: true })
}

3.2、load DOMContentLoaded 监听

  • 当纯 HTML 被完全加载以及解析时,DOMContentLoaded事件会被触发,不用等待 css、img、iframe 加载完
  • 当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发 load 事件
export function observerLoad() {
    ['load', 'DOMContentLoaded'].forEach(type => onEvent(type))
}

function onEvent(type) {
    function callback() {
        lazyReport('performance', {
            subType: type.toLocaleLowerCase(),
            pageURL: getPageURL(),
            startTime: performance.now(),
        });

        window.removeEventListener(type, callback, true)
    }

    window.addEventListener(type, callback, true)
}

3.3、xhr 请求耗时监听

// xhr 请求耗时
export function overwriteOpenAndSend() {
    originalProto.open = function newOpen(...args) {
        this.url = args[1]
        this.method = args[0]
        originalOpen.apply(this, args)
    }

    originalProto.send = function newSend(...args) {
        this.startTime = Date.now()

        const onLoadend = () => {
            this.endTime = Date.now()
            this.duration = this.endTime - this.startTime

            const { status, duration, startTime, endTime, url, method } = this
            const reportData = {
                status,
                duration,
                startTime,
                endTime,
                url,
                method: (method || 'GET').toUpperCase(),
                success: status >= 200 && status < 300,
                subType: 'xhr',
                type: 'performance',
            }
            lazyReport('performance-xhr', reportData)
            
            this.removeEventListener('loadend', onLoadend, true)
        }

        this.addEventListener('loadend', onLoadend, true)
        originalSend.apply(this, args)
    }
}

4、数据上报方法

4.1、xhr 上报

  • 通过xhr上报,如果设置成异步的时候,当用户跳转新页面或者关闭页面时就会丢失当前这个请求,如果设置成同步,又会让页面造成卡顿的现象

4.2、Image的形式来发送请求

  • img标签的方式是通过将埋点数据伪装成图片URL的请求方式,这样就避免了跨域的问题,但是因为浏览器对url的长度会有限制,所以通过这种方式上报不适合大数据量上报的场景

4.3、Navigator.sendBeacon

  • sendBeacon可以说是为埋点量身定做的,这种方式不会有跨域的限制,也不会存在因为刷新页面等情况造成数据丢失的情况,唯一的缺点就是在某些浏览器上存在兼容性的问题

4.4、采用 sendBeacon 上报和 img 标签上报结合的方式

export function report(type, params) {
  const appId = window['_monitor_app_id_'];
  const userId = window['_monitor_user_id_'];
  const url = window['_monitor_report_url_'];

  const logParams = {
    appId, // 项目的appId
    userId,
    type, // 上报信息类型
    data: params, // 上报的数据
    currentTime: new Date().getTime(), // 时间戳
    currentPage: window.location.href, // 当前页面
    ua: navigator.userAgent, // ua信息
  };

  let logParamsString = JSON.stringify(logParams);

  if (navigator.sendBeacon) { 
    // 支持sendBeacon的浏览器
    navigator.sendBeacon(url, logParamsString);
  } else {
    let oImage = new Image();
    oImage.src = `${url}?logs=${logParamsString}`;
  }
}

5、上报时机

  • 采用 requestIdleCallback/setTimeout 延时上报
  • beforeunload 回调函数里上报
  • 缓存上报数据,达到一定数量后再上报
  • 一般情况下是三种方式一起使用
// 防止卸载时还有剩余的埋点数据没发送
window.addEventListener('unload', () => {
  const data = getCache();
  report(data);
});
  
// 延时、合并上报
const cache = [];

export function getCache() {
  return cache;
}

export function addCache(data) {
  cache.push(data);
}

// lazyReport.js
export function lazyReport(type, params) {
  // ....
  const data = getCache();

  if (delay === 0) { // delay=0相当于不做延迟上报
    report(data);
    return;
  }

  if (data.length > 10) { // 数据达到10条上报
    report(data);
    clearTimeout(timer);
    return;
  }

  clearTimeout(timer);
  timer = setTimeout(() => { // 合并上报
    report(data);
  }, delay);
}

5、sdk 初始化信息配置

function init(options) {
  const { 
    appId,  // 系统id
    userId, // 用户id
    reportUrl, // 后端url
    autoTracker, // 自动埋点
    delay, // 延迟和合并上报的功能
    hashPage, // 是否hash录有
    errorReport // 是否开启错误监控
  } = options;

  if (appId) {
    window['_monitor_app_id_'] = appId;
  }
  if (userId) {
    window['_monitor_user_id_'] = userId;
  }
  if (reportUrl) {
    window['_monitor_report_url_'] = reportUrl;
  }
  if (delay) {
    window['_monitor_delay_'] = delay;
  }

  // 是否开启错误监控
  if (errorReport) {
    errorTrackerReport();
  }

  // 是否开启无痕埋点
  if (autoTracker) {
    autoTrackerReport();
  }

  // 路由监听
  if (hashPage) {
    hashPageTrackerReport(); // hash路由上报
  } else {
    historyPageTrackerReport(); // history路由上报
  }

  // DOMContentLoaded、load 监听
  observerLoad()

  // FCP 监听
  observePaint()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/788577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

去重排序2——set

题目描述 输入 个正整数 ​ &#xff0c;按照从大到小的顺序输出不重复的数。 输入格式 第一行一个整数 n 。 第二行 个用空格隔开的正整数 ​ 。 输出格式 每行一个正整数&#xff0c;为从大到小排序后的不重复的数。 样例 #1 样例输入 #1 8 1 3 4 2 2 2 3 1样例输出 #1 4 3…

Linux Mint 21.2 “Victoria “现已可供下载

导读Linux Mint 21.2 “Victoria “发行版今天出现在该项目全球稳定镜像上&#xff0c;这意味着开发者将很快发布官方公告&#xff0c;通知想要下载最新Linux Mint版本的用户。 Linux Mint 21.2从2023年6月21日开始进行公开测试&#xff0c;这给了开发者足够的时间来修复剩余的…

redis(11):springboot中使用redis

1 创建springboot项目 2 创建pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http:/…

同样是测试点工,他凭啥薪资比我高?

如果别人在功能测试方面薪资比你高那是因为做得比你好&#xff0c;别人功能测试做的比你好可能有以下几个原因&#xff1a; 测试策略和方法&#xff1a;别人可能采用了更有效的测试策略和方法&#xff0c;能够更全面地覆盖功能的各个方面。他们可能有更深入的测试计划和设计&a…

【云驻共创】CodeArts Repo ---高效代码协同开发之旅

目录 一、代码托管发展史 1.1 第一代 1.2 第二代 1.3 第三代 二、CodeArts Repo 介绍 二、CodeArts Repo 功能架构 2.1 研发协同 2.2 代码管理功能 2.3 代码存储特性 三、CodeArts Repo 技术能力 三、华为云代码托管技术发展历程 四、CodeAr…

《重构的时机和方法》一本值得程序员都认真读的书

写在前面 《重构的时机和方法》是一本关于软件开发中重构技术的书籍。它以独特的风格和内容优势&#xff0c;为读者提供了全面而易于理解的指导&#xff0c;帮助他们在实际项目中应用重构技术&#xff0c;提高代码质量和开发效率。这本书由两个不同风格的部分组成&#xff0c;…

前端面试题-浏览器相关

1 cookie和localSrorage、session、indexDB 的区别 从上表可以看到&#xff0c; cookie 已经不建议⽤于存储。如果没有⼤量数据存储需求的话&#xff0c;可以使⽤ localStorage 和 sessionStorage 。对于不怎么改变的数据尽量使⽤ localStorage 存储&#xff0c;否则可以⽤ se…

java项目之人事管理系统(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的人事管理系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&…

【力扣每日一题】2023.7.24 宝石与石头

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码运行结果&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们一个字符串表示宝石的类型&#xff0c;再给我们一个字符串表示我们已经拥有的石头&#xff0c;问我们在石头中能找到多少宝…

一文助你快速提高嵌入式软件的代码质量【下】

一文助你快速提高嵌入式软件的代码质量 文章目录 一文助你快速提高嵌入式软件的代码质量&#x1f468;‍&#x1f3eb;前言1️⃣写直观的代码2️⃣写无懈可击的代码3️⃣正确处理错误4️⃣正确处理null指针5️⃣防止过度工程&#x1f647;文末小结 &#x1f468;‍&#x1f3eb…

Python 快速简单搭建HTTP本地服务器,内网通过浏览器访问

1 下载python https://www.python.org/downloads/ 2 安装python&#xff0c;安装时候选择把path加入电脑环境变量 3 由于python内建了简单http服务包&#xff0c;因此对于python来说&#xff0c;只需输入一行命令&#xff0c;就能轻松打开http服务。当然&#xff0c;要运行网页…

C++之栈和堆申请内存(一百六十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【复习16-18天】【我们一起60天准备考研算法面试(大全)-第二十四天 24/60】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

【复习42-44题】【每天40分钟,我们一起用50天刷完 (剑指Offer)】第三十四天 34/50

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

消息队列(一)-- RabbitMQ入门(1)

初识 RabbitMQ 核心思想&#xff1a;接收并转发消息。可以把它想象成一个邮局。 producer&#xff1a;生产者 queue&#xff1a;队列 consumer&#xff1a;消费者什么是消息队列 MQ&#xff08;Message Queue&#xff09;&#xff1a;本质是队列&#xff0c;FIFO先入先出&…

数字IC必学之《SKILL语法用户手册》建议收藏!

熟悉我的同学都知道&#xff0c;一直以来&#xff0c;我都会为大家分享IC各个岗位的学习资料。前端、后端、验证、版图等等&#xff0c;为大家分享了很多。当然也有一些IC入行需要学习的一些语言资料。去年在各个平台更新了一篇关于SKILL的资料&#xff1a; 《Skill入门教程》…

常用API学习07(Java)

Date 在jdk1.8之前,java中的日期和时间是一类的&#xff0c;从1.8之后对日期和时间体系重新做了规划&#xff0c;划分出一个新的包 - java.time包&#xff0c;这个包中包含了日期、时间、时区、日历、单位。 Date&#xff0c;是java中最老的日期和时间类&#xff0c;后续退出…

(原创)Flutter与Native通信的方式:EventChannel和BasicMessageChannel

前言 上一篇博客主要介绍了MethodChannel的使用方式 Flutter与Native通信的方式&#xff1a;MethodChannel 这篇博客接着讲另外两种通信方式 EventChannel和BasicMessageChannel EventChannel用于从native向flutter发送通知事件&#xff0c;例如flutter通过其监听Android的重…

视频文件批量添加字幕内容需要如何快速操作

有时候我们在剪辑视频的过程中&#xff0c;想要给视频素材添加上一些文字说明&#xff0c;需要如何操作呢&#xff1f;为了提高剪辑效率&#xff0c;今天小编来分享教学&#xff0c;教你如何才能批量地给视频素材添加滚动字幕&#xff0c;一起来看看具体的方法介绍吧。 我们先打…

【C++】-模板进阶(让你更好的使用模板创建无限可能)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …