Vue2怎么搭建前端性能/错误/行为监控体系

在 Vue2 项目中搭建前端监控体系,核心要覆盖「性能监控、错误监控、用户行为监控」三大维度,以下是一套可落地的完整方案,包含技术选型、代码实现、数据上报、可视化分析全流程:

一、整体架构设计

监控体系分为 3 层,轻量化接入且不影响主流程性能:

js 复制代码
前端采集层(Vue2 项目)→ 数据上报层(接口/埋点平台)→ 后台分析层(可视化/告警)
  • 采集层:通过 Vue 钩子、浏览器 API 采集性能 / 错误 / 行为数据;
  • 上报层:批量 + 防抖上报,避免频繁请求;
  • 分析层:可选开源工具(Grafana/ELK)或第三方平台(Fundebug / 监控宝)。

二、性能监控(核心监控首屏 / 页面加载 / 接口性能)

1. 核心采集指标

  • 页面性能:FP/FCP/LCP/TTI(通过 performance API);
  • 接口性能:请求耗时 / 成功率 / 状态码;
  • Vue 性能:组件渲染耗时(通过 Vue 钩子)。

2. 代码实现(Vue2 中封装性能监控模块)

新建 src/utils/performanceMonitor.js

js 复制代码
/**
 * 性能监控核心模块
 */
export default {
  // 初始化性能监控
  init() {
    // 1. 页面加载性能(DOMContentLoaded 后采集)
    window.addEventListener('DOMContentLoaded', this.collectPagePerformance);
    // 2. 接口性能监控(重写 axios)
    this.wrapAxios();
    // 3. Vue 组件渲染性能(全局混入)
    this.wrapVueComponent();
  },

  // 采集页面核心性能指标
  collectPagePerformance() {
    const perf = performance.getEntriesByType('navigation')[0];
    if (!perf) return;

    // 核心性能指标(对应首屏/加载相关)
    const performanceData = {
      type: 'performance',
      pageUrl: window.location.href,
      timestamp: Date.now(),
      // 基础加载指标
      domReadyTime: perf.domContentLoadedEventEnd - perf.navigationStart, // DOM 就绪时间
      loadTime: perf.loadEventEnd - perf.navigationStart, // 页面完全加载时间
      // 关键绘制指标
      fp: this.getFP(), // 首次绘制
      fcp: this.getFCP(), // 首次内容绘制
      lcp: this.getLCP(), // 最大内容绘制
    };

    // 上报性能数据(防抖批量上报)
    this.report(performanceData);
  },

  // 获取 FP(首次绘制)
  getFP() {
    const entries = performance.getEntriesByType('paint');
    const fpEntry = entries.find(item => item.name === 'first-paint');
    return fpEntry ? fpEntry.startTime : 0;
  },

  // 获取 FCP(首次内容绘制)
  getFCP() {
    const entries = performance.getEntriesByType('paint');
    const fcpEntry = entries.find(item => item.name === 'first-contentful-paint');
    return fcpEntry ? fcpEntry.startTime : 0;
  },

  // 获取 LCP(最大内容绘制)
  getLCP() {
    return new Promise(resolve => {
      new PerformanceObserver((entryList) => {
        const entries = entryList.getEntries();
        const lcpEntry = entries[entries.length - 1];
        resolve(lcpEntry ? lcpEntry.startTime : 0);
      }).observe({ type: 'largest-contentful-paint', buffered: true });
    });
  },

  // 重写 axios 监控接口性能
  wrapAxios() {
    const axios = require('axios');
    const originalRequest = axios.request;

    axios.request = function (config) {
      const startTime = Date.now();
      // 发送请求
      return originalRequest.call(this, config).then(
        (response) => {
          // 成功:采集耗时/状态码
          this.report({
            type: 'api',
            pageUrl: window.location.href,
            timestamp: Date.now(),
            url: config.url,
            method: config.method,
            status: response.status,
            costTime: Date.now() - startTime,
            isSuccess: true,
          });
          return response;
        },
        (error) => {
          // 失败:采集错误信息
          this.report({
            type: 'api',
            pageUrl: window.location.href,
            timestamp: Date.now(),
            url: config.url,
            method: config.method,
            status: error.response?.status || 'network error',
            costTime: Date.now() - startTime,
            isSuccess: false,
            errorMsg: error.message,
          });
          return Promise.reject(error);
        }
      );
    };
  },

  // 监控 Vue 组件渲染耗时
  wrapVueComponent() {
    Vue.mixin({
      beforeCreate() {
        this.$performanceStart = Date.now();
      },
      mounted() {
        const renderTime = Date.now() - this.$performanceStart;
        // 仅监控渲染耗时 > 100ms 的组件(过滤小组件,减少数据量)
        if (renderTime > 100) {
          this.report({
            type: 'vue-component',
            pageUrl: window.location.href,
            timestamp: Date.now(),
            componentName: this.$options.name || 'unknown',
            renderTime,
          });
        }
      },
    });
  },

  // 数据上报(防抖+批量,避免频繁请求)
  report(data) {
    // 1. 缓存数据到数组
    window.__monitorCache = window.__monitorCache || [];
    window.__monitorCache.push(data);

    // 2. 防抖上报(5s 内只上报一次,或缓存超过 10 条立即上报)
    clearTimeout(window.__monitorTimer);
    if (window.__monitorCache.length >= 10) {
      this.sendReport();
    } else {
      window.__monitorTimer = setTimeout(() => {
        this.sendReport();
      }, 5000);
    }
  },

  // 发送上报请求(对接后台/第三方平台)
  sendReport() {
    const cache = window.__monitorCache || [];
    if (cache.length === 0) return;

    // 上报接口(替换为你的后台接口)
    axios.post('/api/monitor/report', {
      data: cache,
      userAgent: navigator.userAgent,
      userId: localStorage.getItem('userId') || 'anonymous', // 关联用户
    }).catch(err => {
      // 上报失败:缓存到 localStorage,下次重试
      const failCache = JSON.parse(localStorage.getItem('monitorFailCache') || '[]');
      failCache.push(...cache);
      localStorage.setItem('monitorFailCache', JSON.stringify(failCache));
    });

    // 清空缓存
    window.__monitorCache = [];
  },
};

3. 在 Vue2 入口挂载

修改 src/main.js

js 复制代码
import Vue from 'vue';
import App from './App.vue';
import performanceMonitor from './utils/performanceMonitor';

// 初始化性能监控(生产环境才开启)
if (process.env.NODE_ENV === 'production') {
  performanceMonitor.init();
}

new Vue({
  el: '#app',
  render: h => h(App)
});

三、错误监控(捕获代码 / 接口 / Vue 运行时错误)

1. 核心捕获场景

  • Vue 运行时错误(通过 Vue.config.errorHandler);
  • JS 全局错误(window.onerror);
  • 资源加载错误(图片 / 脚本 / 样式,window.addEventListener('error'));
  • Promise 未捕获错误(window.addEventListener('unhandledrejection'))。

2. 代码实现(新建 src/utils/errorMonitor.js

js 复制代码
/**
 * 错误监控核心模块
 */
export default {
  init() {
    // 1. 捕获 Vue 运行时错误
    this.catchVueError();
    // 2. 捕获 JS 全局错误
    this.catchJsError();
    // 3. 捕获资源加载错误
    this.catchResourceError();
    // 4. 捕获 Promise 未处理错误
    this.catchPromiseError();
  },

  // 捕获 Vue 内部错误
  catchVueError() {
    Vue.config.errorHandler = (err, vm, info) => {
      this.report({
        type: 'vue-error',
        pageUrl: window.location.href,
        timestamp: Date.now(),
        message: err.message,
        stack: err.stack,
        component: vm.$options.name || 'unknown',
        info: info, // Vue 错误信息(如生命周期钩子)
      });
    };
  },

  // 捕获 JS 全局错误
  catchJsError() {
    window.onerror = (message, source, line, column, error) => {
      this.report({
        type: 'js-error',
        pageUrl: window.location.href,
        timestamp: Date.now(),
        message,
        source, // 错误文件地址
        line, // 行号
        column, // 列号
        stack: error?.stack || '',
      });
      // 阻止默认行为,避免浏览器控制台重复输出
      return true;
    };
  },

  // 捕获资源加载错误(图片/脚本/样式)
  catchResourceError() {
    window.addEventListener('error', (e) => {
      const target = e.target;
      // 过滤资源错误(非元素错误跳过)
      if (target instanceof HTMLImageElement || target instanceof HTMLLinkElement || target instanceof HTMLScriptElement) {
        this.report({
          type: 'resource-error',
          pageUrl: window.location.href,
          timestamp: Date.now(),
          resourceType: target.tagName.toLowerCase(),
          resourceUrl: target.src || target.href,
          message: `Resource load failed: ${target.src || target.href}`,
        });
      }
    }, true); // 捕获阶段触发,避免冒泡丢失
  },

  // 捕获 Promise 未处理错误
  catchPromiseError() {
    window.addEventListener('unhandledrejection', (e) => {
      this.report({
        type: 'promise-error',
        pageUrl: window.location.href,
        timestamp: Date.now(),
        message: e.reason?.message || 'Promise rejection',
        stack: e.reason?.stack || '',
      });
      // 阻止默认行为
      e.preventDefault();
    });
  },

  // 复用性能监控的上报方法(统一上报入口)
  report: performanceMonitor.report,
};

3. 挂载到入口(main.js

js 复制代码
import errorMonitor from './utils/errorMonitor';

if (process.env.NODE_ENV === 'production') {
  performanceMonitor.init();
  errorMonitor.init(); // 新增错误监控
}

四、用户行为监控(还原操作路径,定位问题根因)

1. 核心监控行为

  • 页面跳转(路由变化);
  • 点击事件(按钮 / 链接);
  • 输入行为(表单);
  • 接口调用(和错误 / 性能关联)。

2. 代码实现(新建 src/utils/behaviorMonitor.js

js 复制代码
/**
 * 用户行为监控核心模块
 */
export default {
  init() {
    // 1. 监控路由变化(Vue Router)
    this.catchRouterChange();
    // 2. 全局监听点击事件
    this.catchClickEvent();
    // 3. 监控表单输入
    this.catchFormInput();
  },

  // 监控路由变化
  catchRouterChange() {
    const router = require('./router/index.js'); // 引入路由实例
    router.afterEach((to, from) => {
      this.report({
        type: 'router-change',
        timestamp: Date.now(),
        from: from.fullPath,
        to: to.fullPath,
        pageTitle: to.meta.title || 'unknown',
      });
    });
  },

  // 全局监听点击事件(过滤非业务元素)
  catchClickEvent() {
    document.addEventListener('click', (e) => {
      const target = e.target;
      // 只监控带 data-behavior 标记的元素(避免全量采集)
      const behaviorKey = target.getAttribute('data-behavior');
      if (!behaviorKey) return;

      this.report({
        type: 'click',
        timestamp: Date.now(),
        pageUrl: window.location.href,
        behaviorKey, // 自定义标记(如 "btn-submit-order")
        targetText: target.innerText.trim(),
        targetSelector: this.getSelector(target), // 元素选择器
      });
    }, true);
  },

  // 监控表单输入(带 data-behavior 的输入框)
  catchFormInput() {
    document.addEventListener('input', (e) => {
      const target = e.target;
      const behaviorKey = target.getAttribute('data-behavior');
      if (!behaviorKey || !['INPUT', 'TEXTAREA'].includes(target.tagName)) return;

      this.report({
        type: 'form-input',
        timestamp: Date.now(),
        pageUrl: window.location.href,
        behaviorKey,
        inputValue: target.value.substring(0, 100), // 只采集前100字符,避免敏感信息
      });
    }, true);
  },

  // 获取元素唯一选择器(便于定位)
  getSelector(el) {
    if (!el) return '';
    let selector = el.tagName.toLowerCase();
    if (el.id) selector += `#${el.id}`;
    if (el.className) selector += `.${el.className.replace(/\s+/g, '.')}`;
    return selector;
  },

  // 复用上报方法
  report: performanceMonitor.report,
};

3. 业务中埋点使用

在需要监控的元素上添加 data-behavior 标记:

vue 复制代码
<template>
  <!-- 按钮点击监控 -->
  <button data-behavior="btn-home-search" @click="search">搜索</button>
  
  <!-- 表单输入监控 -->
  <input data-behavior="input-home-keyword" v-model="keyword" placeholder="请输入关键词">
</template>

4. 挂载到入口(main.js

js 复制代码
import behaviorMonitor from './utils/behaviorMonitor';

if (process.env.NODE_ENV === 'production') {
  performanceMonitor.init();
  errorMonitor.init();
  behaviorMonitor.init(); // 新增行为监控
}

五、进阶优化(避免监控本身影响性能)

1. 白名单 / 黑名单

js 复制代码
// 上报时过滤无需监控的页面/接口
report(data) {
  const blacklist = ['/login', '/test']; // 过滤测试/登录页
  if (blacklist.includes(window.location.pathname)) return;
  // ... 原有逻辑
}

2. 敏感信息过滤

js 复制代码
// 上报前过滤手机号/密码等敏感信息
filterSensitiveData(data) {
  if (data.inputValue) {
    data.inputValue = data.inputValue.replace(/1[3-9]\d{9}/g, '****'); // 手机号脱敏
  }
  return data;
}

3. 离线缓存重试

上报失败时缓存到 localStorage,下次页面加载时重试:

js 复制代码
// 在 sendReport 失败后
const failCache = JSON.parse(localStorage.getItem('monitorFailCache') || '[]');
failCache.push(...cache);
// 限制缓存大小(最多 100 条)
if (failCache.length > 100) failCache.splice(0, failCache.length - 100);
localStorage.setItem('monitorFailCache', JSON.stringify(failCache));

// 页面加载时重试
window.addEventListener('load', () => {
  const failCache = JSON.parse(localStorage.getItem('monitorFailCache') || '[]');
  if (failCache.length > 0) {
    axios.post('/api/monitor/report', { data: failCache })
      .then(() => localStorage.removeItem('monitorFailCache'));
  }
});

六、数据可视化与告警(可选方案)

1. 开源方案(自建)

  • 数据存储:MySQL/ClickHouse(适合海量数据);
  • 可视化:Grafana/ELK(Kibana);
  • 告警:Prometheus + AlertManager(触发阈值时邮件 / 钉钉告警)。

2. 第三方平台(快速接入)

  • 错误监控:Fundebug、Sentry、FrontJS;
  • 性能监控:阿里云 ARMS、百度统计、腾讯监控宝;
  • 行为分析:百度统计、友盟 +、GrowingIO。

3. 核心告警阈值参考

指标 告警阈值
JS 错误率 > 0.1%
接口失败率 > 1%
LCP > 4s
首屏加载时间 > 5s

七、最终集成(main.js 完整示例)

js 复制代码
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import axios from 'axios';
import performanceMonitor from './utils/performanceMonitor';
import errorMonitor from './utils/errorMonitor';
import behaviorMonitor from './utils/behaviorMonitor';

// 生产环境开启监控
if (process.env.NODE_ENV === 'production') {
  // 全局挂载 axios(供监控模块使用)
  window.axios = axios;
  // 初始化监控
  performanceMonitor.init();
  errorMonitor.init();
  behaviorMonitor.init();
}

new Vue({
  router,
  el: '#app',
  render: h => h(App)
});

这套方案轻量化、可扩展,既可以对接自建后台,也能快速切换到第三方监控平台,核心优势是:

  1. 无侵入:通过全局混入、事件监听实现,不修改业务代码;
  2. 高性能:批量 + 防抖上报,本地缓存重试,不阻塞主流程;
  3. 全覆盖:性能 / 错误 / 行为维度完整,可还原问题现场。

Vue2中项目实战高频用到的设计模式有哪些

在 Vue2 项目实战中,以下是高频用到的设计模式,结合 Vue2 的特性(如选项式 API、组件化、响应式),它们能有效提升代码的可维护性和复用性:

1. 单例模式

  • 核心场景:全局唯一的实例(如 Vuex Store、全局弹窗、请求拦截器)。

  • 实战示例

    • Vuex 的store是单例,全局共享状态;
    • 全局提示组件(如Toast),通过Vue.prototype.$toast = new Toast()挂载为单例,避免重复创建实例。

2. 工厂模式

  • 核心场景:批量创建相似的组件 / 实例,避免重复代码。

  • 实战示例

    • 表单控件工厂:根据配置(如{ type: 'input', label: '姓名' })动态生成InputSelect等表单组件;
    • 弹窗工厂:封装createModal函数,传入组件和参数,自动创建并挂载弹窗实例。

3. 观察者模式(发布 - 订阅模式)

  • 核心场景 :Vue2 的响应式系统本身基于观察者模式(data是被观察者,模板 / 计算属性是观察者);此外常用于跨组件通信。

  • 实战示例

    • Vue.prototype.$bus = new Vue()实现全局事件总线(发布this.$bus.$emit('event'),订阅this.$bus.$on('event', () => {}));
    • 计算属性依赖data变化自动更新。

4. 代理模式

  • 核心场景 :对复杂操作进行封装,对外暴露简化的接口(如请求代理、权限代理)。

  • 实战示例

    • 封装api.js 作为接口代理,统一处理请求的 URL 拼接、参数格式化、错误拦截;
    • 权限代理组件 :封装一个AuthButton组件,根据用户权限决定按钮是否渲染 / 可用。

5. 装饰器模式

  • 核心场景在不修改原组件的前提下,动态扩展其功能 (Vue2 中常用mixin或高阶组件实现)。

  • 实战示例

    • mixin给多个组件添加 "加载状态管理" 功能(如loadingMixin包含loading数据和setLoading方法);
    • 高阶组件(HOC) :封装withAuth函数,接收组件并返回 "带权限校验" 的新组件。

6. 策略模式

  • 核心场景将多种 "算法 / 逻辑" 封装为独立策略 ,根据条件动态切换(替代大量if-else)。

  • 实战示例

    • 表单验证策略 :定义requiredemailmaxLength等验证策略,根据表单项的rules动态选择;
    • 支付方式策略:封装AlipayWechatPay等支付策略,根据用户选择切换支付逻辑。

这些模式在 Vue2 项目中并非孤立使用(例如 "组件化 + 策略模式" 实现动态表单),结合 Vue2 的特性(如 mixin、Vuex、事件总线)能更好地落地。

相关推荐
神秘的猪头2 小时前
🎉 React 的 JSX 语法与组件思想:开启你的前端‘搭积木’之旅(深度对比 Vue 哲学)
前端·vue.js·react.js
江公望2 小时前
VUE3 data()函数浅谈
前端·javascript·vue.js
江公望3 小时前
VUE3 defineProps 5分钟讲清楚
前端·javascript·vue.js
a程序小傲3 小时前
京东Java面试被问:ZGC的染色指针如何实现?内存屏障如何处理?
java·后端·python·面试
计算机毕设VX:Fegn08953 小时前
计算机毕业设计|基于Java + vue水果商城系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·课程设计
周杰伦_Jay3 小时前
【 Vue前端技术详细解析】目录结构与数据传递
前端·javascript·vue.js
奋斗吧程序媛4 小时前
动态组件驱动的标签页架构(简单来说:一个页面包含许多Tabs页面,这些Tabs页面渲染逻辑)
前端·javascript·vue.js
Felix_Fly4 小时前
用 Vue3 + naive-cron 开发 Cron 表达式工具:从 0 到 1 实现生成 + 反解析
前端·javascript·vue.js·vue·cron·naive
程序员清风4 小时前
别卷模型了!上下文工程才是大模型应用的王道!
java·后端·面试