入门前端监控

背景

  • 前端监控是指通过一系列手段对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、用户埋点统计

  • 埋点是监控用户在我们应用上的一些动作表现,埋点又分为手动埋点和无痕埋点
  • 手动埋点就是手动的在代码里面添加相关的埋点代码,比如用户点击某个按钮,就在这个按钮的点击事件中加入相关的埋点代码
<buttononClick={() => {// 业务代码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.pushStatewindow.addEventListener('pushState', function () {listener()});// history.replaceStatewindow.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.pushStatewindow.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) returnconst entryHandler = (list) => {        for (const entry of list.getEntries()) {if (entry.name === 'first-contentful-paint') {observer.disconnect()}const json = entry.toJSON()delete json.durationconst 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.startTimeconst { status, duration, startTime, endTime, url, method } = thisconst 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, // 项目的appIduserId,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,  // 系统iduserId, // 用户idreportUrl, // 后端urlautoTracker, // 自动埋点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.mzph.cn/news/7866.shtml

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

相关文章

Java连锁门诊医院HIS信息管理系统源码

Java连锁门诊医院HIS信息管理系统源码&#xff1a;SaaS运维平台多医院多机构多门诊入驻强大的电子病历完整开发文档 一、系统概述 ❉采用主流成熟技术&#xff0c;软件结构简洁、代码规范易阅读&#xff0c;SaaS应用&#xff0c;全浏览器访问前后端分离&#xff0c;多服务协同…

通过两种实现方式理解CANoe TC8 demo是如何判断接收的以太网报文里的字段的

假设有一个测试用例,需求是:编写一个测试用例,发送一条icmpv4 echo request报文给DUT,identifier字段设置为10。判断DUT能够回复icmpv4 echo reply报文,且identifier字段值为10。 实现:在canoe的simulation setup界面插入一个test节点,ip地址为:192.168.0.1,mac地址为…

具身智能,是机器人的“冷饭热炒”吗?

大模型正如火如荼&#xff0c;下一个AI风口就来了。 如果你关注2023世界人工智能大会等行业峰会&#xff0c;以及英伟达、微软、谷歌、特斯拉和国內科技大厂的最新发布会&#xff0c;除了“大模型”&#xff0c;应该会听到另一个高频词——具身智能。 所谓具身智能Embodied AI …

IRIS搭建docker

之前把web实现了docker&#xff0c;开发或测试环境可能需要开发自己搭数据库&#xff0c;为了方便使用&#xff0c;把数据库也做一个docker。 由于原生的CentOS我还有改yum仓库&#xff0c;所以这次从之前lis搞的改好yum的镜像开始&#xff08;从改好yum的lisnew的镜像创建lis…

【Linux】Ubuntu基本使用与配置, 以及常见问题汇总(一)

前言 大学期间&#xff0c;感觉很多时候学习课外知识都是被推着往前走&#xff0c;很多内容并没有深入去学习&#xff0c;知识的记录受限于所学比较片面&#xff0c;如今渐渐意识到似乎并没有建立起相关知识的体系架构&#xff0c;缺乏一个系统学习并整理的过程。本文将以Ubunt…

grid map学习笔记1之Ubuntu18.04+ROS-melodic编译安装grid_map栅格地图及示例运行

文章目录 0 引言1 安装依赖和编译1.1 安装依赖1.2 下载编译 2 运行示例2.1 simple_demo2.2 tutorial_demo2.3 iterators_demo2.4 image_to_gridmap_demo2.5 grid_map_to_image_demo2.6 opencv_demo2.7 resolution_change_demo2.8 filters_demo2.9 interpolation_demo 0 引言 苏…

labview 多线程同步

所谓通讯的同步是指多个线程同时进行或严格按照顺序执行&#xff0c;数据的严格性是指发送多少数据接收多少数据&#xff0c;不能出现数据丢失或重复接收的现象。 labview的同步机制有事件发生、集合点、通知器、信号量。 可以这么来记忆&#xff1a;事急&#xff08;集&…

JavaScript |(一)JavaScript简介及基本语法 | 尚硅谷JavaScript基础实战

学习来源&#xff1a;尚硅谷JavaScript基础&实战丨JS入门到精通全套完整版 文章目录 &#x1f4da;JavaScript简介&#x1f407; 实现&#x1f407;JavaScript的特点 &#x1f4da;基本知识&#x1f407;编写位置&#x1f525;方式一&#xff1a;在标签中写&#xff08;不推…

【超全面】Linux嵌入式干货学习系列教程

文章目录 一、前言二、Linux基础篇三、数据结构与算法基础三、Linux应用篇四、Linux网络篇五、ARM篇六、Linux系统移植篇七、Linux驱动篇八、Linux特别篇九、Linux项目篇 一、前言 博主学习Linux也有几个月了&#xff0c;在这里为广大朋友整理出嵌入式linux的学习知识&#xff…

zookeeper-3.7.1集群

1.下载&解压安装包apache-zookeeper-3.7.1-bin.tar.gz 解压到/app/ &改名zookeeper-3.7.1 [rootnode1 app]# tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz -C /app/ [rootnode1 app]# mv apache-zookeeper-3.7.1-bin zookeeper-3.7.1 ---- 删除docs [rootnode1…

【2023裸辞失业后之初学RocketMQ】

目录 RocketMQ概述MQ概述常见的MQ产品常见的协议 Rocket的安装和启动基本概念系统架构安装RocketMQ和控制台 RocketMQ概述 MQ概述 Message Queue&#xff1a;是提供消息队列服务的中间件&#xff0c;提供消息生产&#xff0c;存储&#xff0c;消费的全过程。 作用&#xff1a…

矩阵svd分解和矩阵的伪逆

真该好好学习一下Latex数学公式的语法和规则了&#xff0c;否则&#xff0c;连写个博客都没法写&#xff0c;这叫什么事&#xff01; https://blog.csdn.net/ViatorSun/article/details/82826664 直接上数学博士写的ppt图&#xff08;肯定比我在这里胡说八道强的多&#xff0…

css - Media Query

使用bootstrap的grid system可以在一个较为粗糙的范围得到较好的响应性&#xff0c;但是通过viewport可以看到网站在具体哪个像素点处变得丑陋&#xff0c;再通过css media query来精细调整网页布局。 可以通过media query来提高网页移动响应能力。

四、运算符(2)

本章概要 关系运算符 测试对象等价 逻辑运算符 短路 字面值常量 下划线指数计数法 位运算符 关系运算符 关系运算符会通过产生一个布尔&#xff08;boolean&#xff09;结果来表示操作数之间的关系。如果关系为真&#xff0c;则结果为 true&#xff0c;如果关系为假&#xf…

SpringBoot日志文件

1.日志有什么用&#xff1f; 日志是程序的重要组成部分&#xff0c;如果程序报错&#xff0c;我们可以通过日志发现和定位问题&#xff0c;同样日志也可以实现以下的功能&#xff1a; 记录用户登录日志&#xff0c;方便分析用户是否是正常登录还是恶意破解用户&#xff1b;记…

使用 OpenCV 和 GrabCut 算法进行交互式背景去除

一、说明 我想&#xff0c;任何人都可以尝试从图像中删除背景。当然&#xff0c;有大量可用的软件或工具能够做到这一点&#xff0c;但其中一些可能很昂贵。但是&#xff0c;我知道有人使用窗口绘画3D魔术选择或PowerPoint背景去除来删除背景。 如果您是计算机视觉领域的初学者…

network failed to load response data: no resource with given ide...

Chrome 开发者工具无法显示服务器正常返回的 HTTP 请求 - Failed to load response data 今天做开发时遇到一个问题&#xff0c;Chrome 开发者工具 network 标签里&#xff0c;虽然一个 HTTP 请求已经成功从服务器端返回&#xff0c;但是 Chrome 开发者工具里&#xff0c;仍然…

ArcGIS Engine 与 Visual Studio版本对照表

通过C#对于Arcgis的二次开发&#xff0c;需要Visual Studio版本需要与ArcGIS Engine对应&#xff0c;Visual Studio版本的或高或低都不能使ArcObjects SDK for microsoft.Net framework安装成功。下面是各个版本的对照表。 序号ArcEngine版本visual Studio版本Network版本110.…

Spring 的创建和使用

Spring 就是一个包含了众多工具方法的 IoC 容器。既然是容器那么它就具备两个最基本的功能 将对象存储到容器&#xff08;Spring&#xff09;中&#xff1b; 从容器中将对象取出来 在 Java 语言中对象也叫做 Bean&#xff0c;所以后面咱们再遇到对象就以 Bean 著称 一、创建 …

实训笔记7.25

实训笔记7.25 7.25笔记一、MapReduce的特殊使用场景1.1 通过MapReduce程序实现多文件Join操作1.1.1 通过在Reduce端实现join操作1.1.2 通过在Map端实现join操作 1.2 MapReduce中的计数器的使用1.2.1 计数器使用两种方式 1.3 MapReduce实现数据清洗 二、MapReduce的OutputFormat…