前端监控实现(node+vue)

前端监控

项目地址 git clone https://gitee.com/childe-jia/monitor–front-end.git

背景

思考一下,我们的项目代码在上线之后是不是就不用管了呢?并不是,作为前端开发工程师,我们是直接跟用户打交道的,一个应用的用户留存率跟这个应用的稳定性有很大的关系,因为监控能让我们快速的发现并解决问题,所以一个系统的监控体系越完善这个系统就越稳定。所以不光后端要对接口做监控,前端的监控也显得很重要。

对于我们来说,学习如何做前端的监控一方面可以拓宽我们的知识面,另一方面可以在面试的时候加分,让面试官觉得我们不只是在公司整天的写业务代码,也有技术上的产出,不至于面试的时候问到这方面的问题完全摸不着头脑。

怎么做?
  • 接入现成的

    1. sentry https://docs.sentry.io/

    2. fundebug https://www.fundebug.com/

  • 自己封装

需要监控什么?
  • 错误统计

    首先,我们的代码发布到线上总是会发生奇奇怪怪的错误,错误原因也五花八门,可能是浏览器兼容问题,可能是代码里面没做兜底,也可能是后端接口挂掉了等等错误,可能随便一个错误都会影响用户的使用,所以对线上进行错误监控显的尤为重要,能够让我们第一时间去响应报错并解决。

  • 行为日志埋点

    对于一些常见的电商app,比如淘宝,都有一套自己的用户行为分析的系统,分析用户浏览时间比较长的页面有哪些,常点击的按钮有哪些等等行为,通过分析用户的这些行为去制定不同的策略引导你购物,这些都可以通过前端埋点去实现对用户行为的监控。

  • PV/UV统计

    我们上线那么多的前端页面,肯定特别想知道我们的用户对哪个页面的访问次数比较多,也想知道每天有多少的用户访问我们的系统,这就需要用到PV,UV的统计

所以我们系统的设计就主要围绕上面着三点进行设计,主要流程如下:

在这里插入图片描述

数据采集:数据采集做的就是采集我们系统的监控数据,包括PV,UV和用户行为及前端报错的数据。

日志上报:上报做的就是将第一步采集到的数据发送到服务端。

日志查询:这一步就是在后台查询我们采集并上报的数据,方便对系统进行分析。

我们的SDK做的主要是对前两部分的实现。

功能拆分

环境
  • Mac
  • Node 16.14.2
  • npm 8.5.0
初始化

初始化其实很简单,就是获取用户传过来的参数,然后调用我们的初始化函数就可以了,在初始化函数中,我们可以注入一些监听事件来实现数据统计的功能。

// 1.加载配置 2.监控代码注入
function init(options) {
  loadConfig(options); // 加载配置
}
错误监控

前端是直接和用户打交道的,前端页面报错是很影响用户体验一件事,即使在测试充分后上线也会因为用户的操作行为以及操作的环境出现各种各样的错误,所以,不光是后端需要加报警监控,前端的错误监控也很重要。

错误类型
  1. 语法错误

    语法错误一般在可发阶段就可以发现,比如常见的单词拼写错误,中英文符号错误等。注意:语法错误是无法被try catch捕获的,因为在开发阶段就能发现,所以一般不会发布到线上环境。

    try {
      let name = 'heima; // 少一个单引号
      console.log(name);
    } catch (error) {
      console.log('----捕获到了语法错误-----');
    }
    
  2. 同步错误

    同步错误指的是在js同步执行过程中的错误,比如变量未定义,是可以被try catch给捕获到的

    try {
      const name = 'heima';
      console.log(nam);
    } catch (error) {
      console.log('------同步错误-------')
    }
    
  3. 异步错误

    异步错误指的是在setTimeout等函数中发生的错误,是无法被try catch捕获到的

    try {
      setTimeout(() => {
        undefined.map();
      }, 0);
    } catch (error) {
      console.log('-----异步错误-----')
    }
    

    异步错误的话我们可以用window.onerror来进行处理,这个方法比try catch要强大很多

    /**
     * @param {String}  msg    错误描述
     * @param {String}  url    报错文件
     * @param {Number}  row    行号
     * @param {Number}  col    列号
     * @param {Object}  error  错误Error对象
     */
     window.onerror = function (msg, url, row, col, error) {
       console.log('出错了!!!');
       console.log(msg);
       console.log(url);
       console.log(row);
       console.log(col);
       console.log(error);
    };
    
  4. promise错误

    promise 中使用 catch 可以捕获到异步的错误,但是如果没有写 catch 去捕获错误的话 window.onerror 也捕获不到的,所以写 promise 的时候最好要写上 catch ,或者可以在全局加上 unhandledrejection 的监听,用来监听没有被捕获的promise错误。

    window.addEventListener("unhandledrejection", function(error){
      console.log('捕获到异常:', error);
    }, true);
    
  5. 资源加载错误

    资源加载错误指的是比如一些资源文件获取失败,可能是服务器挂掉了等原因造成的,出现这种情况就比较严重了,所以需要能够及时的处理,网路错误一般用 window.addEventListener 来捕获。

    window.addEventListener('error', (error) => {
      console.log(error);
    }, true);
    

所以SDK错误监控的实现,就是围绕这几种错误实现的。 try-catch 用来在可预见情况下监控特定的错误,window.onerror 主要是来捕获预料之外的错误,比如异步错误。但是 window.onerror 也并不是万能的,它可以捕获语法,同步,异步的错误,但是对于promise错误以及网络错误还是无能为力,所以还需要 unhandledrejection 监听来捕获promise错误,最后,再加上 error 监听捕获资源加载的错误就能将各种类型的错误全覆盖了。

用户埋点统计

埋点是监控用户在我们应用上的一些动作表现,是不是经常感觉有些应用推荐的内容都是自己感兴趣的,这就是埋点这个“内鬼”在搞怪,比如你在淘宝上的某类型的鞋子的页面浏览了几分钟,那么就会有一个“张三在2022-7-16 15:30搜索了某款运动鞋并浏览了十分钟”的记录的上报,后台就可以根据这些上报的数据去分析用户的行为,并且制定之后推送或者产品的迭代优化等,对于产品后续的发展起着重要作用。埋点又分为手动埋点和无痕埋点。

手动埋点

手动埋点就是手动的在代码里面添加相关的埋点代码,比如用户点击某个按钮,就在这个按钮的点击事件中加入相关的埋点代码,或者提交了一个表单,就在这个提交事件中加入埋点代码。

// 方式1
<button
  onClick={() => {
    // 业务代码
  	tracker('click', '用户去支付');
    // tracker('visit', '访问新页面');
    // tracker('submit', '提交表单');
  }}
>手动埋点</button>
// 方式2
<button 
	data-target="支付按钮"
	onClick={() => {
    // 业务代码
  }}
>手动上报</button>
  • 优点:可控性强,可以自定义上报具体的数据。
  • 缺点:对业务代码侵入性强,如果有很多地方需要埋点就得一个一个手动的去添加埋点代码。
无痕埋点

无痕埋点是为了解决手动埋点的缺点,实现一种不用侵入业务代码就能在应用中添加埋点监控的埋点方式。

<button onClick={() => {
  // 业务代码
}}>自动埋点</button>
// 自动埋点实现
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);
};

在这里插入图片描述

  • 优点:不用侵入务代码就能实现全局的埋点。
  • 缺点:只能上报基本的行为交互信息,无法上报自定义的数据;上报次数多,服务器性能压力大。
PV统计

PV即页面浏览量,用来表示该页面的访问数量

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

history路由

history路由依赖全局对象 history 实现的

  • history.back(); // 返回上一页,和浏览器回退功能一样
  • history.forward(); // 前进一页,和浏览器前进功能一样
  • history.go(); // 跳转到历史记录中的某一页, eg: history.go(-1); history.go(1)
  • history.pushState(); // 添加新的历史记录
  • history.replaceState(); // 修改当前的记录项

history路由的实现主要依赖的就是 pushStatereplaceState 来实现的,但是这两种方法不能被 popstate 监听到,所以需要对这两种方法进行重写来实现数据的采集。

/**
 * 重写pushState和replaceState方法
 * @param {*} name 
 * @returns 
 */
const createHistoryEvent = function (name) {
  // 拿到原来的处理方法
  const origin = window.history[name];
  return function(event) {
    if (name === 'replaceState') {
      const { current } = event;
      const pathName = location.pathname;
      if (current === pathName) {
        let res = origin.apply(this, arguments);
        return res;
      }
    }

    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');

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

// history.go()、history.back()、history.forward() 监听
window.addEventListener('popstate', function () {
  listener()
});

// history.pushState
window.addEventListener('pushState', function () {
  listener()
});

// history.replaceState
window.addEventListener('replaceState', function () {
  listener()
});
hash路由

url上hash的改变会出发 hashchange 的监听,所以我们只需要在全局加上一个监听函数,在监听函数中实现采集并上报就可以了。但是在react和vue中,对于hash路由的跳转并不是通过 hashchange 的监听实现的,而是通过 pushState 实现,所以,还需要加上对 pushState 的监听才可以。

export function hashPageTrackerReport() {
  let beforeTime = Date.now(); // 进入页面的时间
  let beforePage = ''; // 上一个页面
  
  // 上报
  function listener() {
    const stayTime = getStayTime();
    const currentPage = window.location.href;
      lazyReport('visit', {
      stayTime,
      page: beforePage,
    })
    beforePage = currentPage;
  }

  // hash路由监听
  window.addEventListener('hashchange', function () {
    listener()
  });
}
UV统计

UV统计的是一天内访问该网站的用户数

uv统计比较简单,就只需要在SDK初始化的时候上报一条消息就可以了

/**
 * 初始化配置
 * @param {*} options 
 */
function init(options) {
  ... // 加载配置
  report('user', '加载应用'); // uv统计
}
数据上报
  1. xhr接口请求

    采用接口请求的方式是最简单的,就跟请求其他业务接口一样,只不过上传的是埋点的数据。但是在通常的情况下,一般在公司里面处理埋点的服务器和处理业务逻辑的处理器不是同一台,所以还需要手动解决跨域的问题,另一方面,如果在上报的过程中刷新或者重新打开新页面,可能会造成埋点数据的缺失,所以传统的xhr接口请求的方式并不能很好的适应埋点的需求。

  2. img标签

    img标签的方式是通过将埋点数据伪装成图片URL的请求方式,这样就避免了跨域的问题,但是因为浏览器对url的长度会有限制,所以通过这种方式上报不适合大数据量上报的场景,而且也会存在刷新或者打开页面的时候上报的数据丢失的情况。

  3. sendBeacon

    鉴于以上两种方式的缺点,sendBeacon应运而生了,sendBeacon可以说是为埋点量身定做的,这种方式不会有跨域的限制,也不会存在因为刷新页面等情况造成数据丢失的情况,唯一的缺点就是在某些浏览器上存在兼容性的问题,所以在日常的开发场景中,通常采用sendBeacon上报和img标签上报结合的方式

 * 上报
 * @param {*} type 
 * @param {*} params 
 */
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, // error/action/visit/user
    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 { // 不支持sendBeacon的浏览器
    let oImage = new Image();
    oImage.src = `${url}?logs=${logParamsString}`;
  }
}
合并上报

对于无痕埋点来说,一次点击就进行一次上报对服务器来说压力有点大,所以最好是能进行一个合并上报。

// cache.js
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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

合并有序链表

合并有序链表 图解代码如下 图解 虽然很复杂&#xff0c;但能够很好的理解怎么使用链表&#xff0c;以及对链表的指针类理解 代码如下 Node* merge_list_two_pointer(List& list1, List& list2) {Node* new_head1 list1.head;Node* new_head2 list2.head;Node* s…

华为---理解OSPF Route-ID(五)

9.5 理解OSPF Route-ID 9.5.1 原理概述 一些动态路由协议要求使用Router-ID作为路由器的身份标示&#xff0c;如果在启动这些路由协议时没有指定Router-ID,则默认使用路由器全局下的路由管理Router-ID。 Router-ID选举规则为&#xff0c;如果通过Router-ID命令配置了Router-…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 局域网中的服务器个数(200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

【Flutter 专题】120 Flutter 腾讯移动通讯 TPNS~

1.2 方法使用 小菜按照官网的介绍尝试了一些常用的 API 方式&#xff0c;主要分为应用类&#xff0c;账号类和标签类三种 API&#xff0c;小菜业务中没有应用账号和标签模块&#xff0c;暂未深入研究&#xff1b; 应用接口 API a. 注册推送服务 对于服务的注册初始化&#x…

【嵌入式Linux】i.MX6ULL 时钟树——理论分析

文章目录 0. 时钟树结构0.1 参考手册 Chapter 18​: Clock Controller Module (CCM)0.2 时钟信号路径 1. 时钟源——晶振1.1 外部低频时钟 - CKIL1.1.1 CKIL 同步到 IPG_CLK 解释 1.2 外部高频时钟 - CKIH 和 内部振荡器1.3 总结1.4 缩写补充 2. PLL时钟2.1 i.MX6U 芯片 PLL 时…

不用写一行代码,deepseek结合腾讯云语音识别来批量转录Mp3音频

首先&#xff0c;打开window系统中的cmd命令行工具&#xff0c;或者powershell&#xff0c;安装腾讯云tencentcloud的Python库 pip install -i https://mirrors.tencent.com/pypi/simple/ --upgrade tencentcloud-sdk-python 然后&#xff0c;开通腾讯云的对象存储COS服务&…

关于DrawTools的分析- 一个优秀的C#开源绘图软件

国外大佬&#xff0c;曾经写过两个关于DrawTools相关的开源绘图软件。 我更新了一个优化的版本如下图&#xff0c;稍后会发布更新给大家。 需要的用户可发邮件给我 448283544qq.com 应用于AGV地图编辑器如下&#xff1a; 那么这个优于很多普通的画布软件&#xff0c;包含点、…

Android进程间通信 Messenger详解

//这里服务端Service是运行在单独的进程中的 android:process“:other” class MessengerService : Service() { private lateinit var mMessenger: Messenger override fun onBind(intent: Intent): IBinder { log(TAG, “onBind~”) //传入Handler实例化Messenger mMes…

Redis数据库的删除和安装

Redis数据库的删除和安装 1、删除Redis数据库2、下载Redis数据库 1、删除Redis数据库 没有下载过的&#xff0c;可以直接跳到下面的安装过程↓ 我们电脑中如果有下载过Redis数据库&#xff0c;要更换版本的话&#xff0c;其实Redis数据库的删除是比较简单的&#xff0c;打开我…

leetcode 二分查找·系统掌握 第一个错误版本

题意&#xff1a; 题解&#xff1a; 就是经典的~01~泛型查找&#xff0c;而且一定存在这样错误的版本所以查找不会"失败"&#xff0c;返回每次查找结果即可。 int firstBadVersion(int n) {long l1,rn,mid;while(l<r){mid(lr)>>1;if(isBadVersion(mid))r…

微积分-导数1(导数与变化率)

切线 要求与曲线 C C C相切于 P ( a , f ( a ) ) P(a, f(a)) P(a,f(a))点的切线&#xff0c;我们可以在曲线上找到与之相近的一点 Q ( x , f ( x ) ) Q(x, f(x)) Q(x,f(x))&#xff0c;然后求出割线 P Q PQ PQ的斜率&#xff1a; m P Q f ( x ) − f ( a ) x − a m_{PQ} \…

csdn上传源码资源卖钱能买房买车吗?每天最高收入200-500?

csdn上传源码卖钱能买房买车吗,最高收入200-500&#xff1f; 作者收入日榜 不***孩 收益617.32元 程***妍 收益534.56元 s***n 收益323.71元 盈***客 收益315.05元 极***计 收益284.17元

[第五空间2019 决赛]PWN5

参考文章: 格式化字符串漏洞原理及其利用&#xff08;附带pwn例题讲解&#xff09;_格式化字符串攻击教程-CSDN博客 格式化字符串漏洞原理详解_静态编译 格式化字符串漏洞-CSDN博客 BUU pwn [第五空间2019 决赛]PWN5 //格式化字符串漏洞 - Nemuzuki - 博客园 (cnblogs.com) …

如果申请小程序地理位置接口权限之前刷到这一篇就好了

小程序地理位置接口有什么功能&#xff1f; 通常情况下&#xff0c;我们在开发小程序时&#xff0c;可能会用到获取用户地理位置信息的功能。小程序开发者开放平台的新规定指出&#xff0c;如果没有申请开通微信小程序地理位置接口&#xff08;getLocation&#xff09;&#xf…

【建议收藏】Android中高级大厂面试源码秘籍,为你备战2021金三银四,直通大厂

首先来说下为什么要读源码&#xff0c;有学习源码的必要吗&#xff1f; 为什么要阅读源码&#xff1f; 关于为什么阅读和学习源码&#xff0c;我个人认为可能有以下几点&#xff1a; &#xff08;一&#xff09;吊打面试官&#xff0c;应对面试 为了找到更好的工作&#xff…

【小沐学AI】Python实现语音识别(Whisper-Web)

文章目录 1、简介2、下载2.1 openai-whisper2.2 whisper-web 结语 1、简介 https://openai.com/index/whisper/ Whisper 是一种自动语音识别 &#xff08;ASR&#xff09; 系统&#xff0c;经过 680,000 小时的多语言和多任务监督数据的训练&#xff0c;从网络上收集。我们表…

Jenkins定时构建自动化(一):Jenkins下载安装配置

目录 ​编辑 一、jdk下载安装 1. 已下载安装jdk 2. 未下载安装jdk 二、jenkins安装 1. .war包安装 三、获取IP地址 四、jenkins网页配置 一、jdk下载安装 1. 已下载安装jdk &#xff08;1&#xff09;查询jdk版本命令&#xff1a;java -version &#xff08;2&#xff09;…

4.XSS-反射型(get)利用:获取cookie

GET反射型XSS利用&#xff1a;获取cookie 修改一下配置文件\pikachu\pkxss\xcookie\cookie.php 我这里将对应的IP地址修改为本地pikachu的主站IP地址&#xff0c;这样给用户造成一种正常视觉上的欺骗&#xff0c;容易上当。重定向到pikachu主页面 基于IP搭建的pkxss平台(入侵…

Python 函数注解,给函数贴上小标签

目录 什么是函数注解? 为什么使用函数注解? 如何编写函数注解? 实战演练 与类型提示(Type Hints)的关系 类型安全的运算器 什么是函数注解? 函数注解(Function Annotations)是Python 3中新增的一个特性,它允许为函数的参数和返回值指定类型。 这些注解不会改变…

高速缓存存储器(Chche)

为了解决CPU和主存之间速度不匹配的问题&#xff0c;计算机系统中引入了高速缓存&#xff08;Chche&#xff09;的概念。 基本想法&#xff1a;使用速度更快但容量更小、价格更高的SRAM制作一个缓冲存储器&#xff0c;用来存放经常用到的信息&#xff1b;这样一来&#xff0c;…