在 Vue2 项目中搭建前端监控体系,核心要覆盖「性能监控、错误监控、用户行为监控」三大维度,以下是一套可落地的完整方案,包含技术选型、代码实现、数据上报、可视化分析全流程:
一、整体架构设计
监控体系分为 3 层,轻量化接入且不影响主流程性能:
js
前端采集层(Vue2 项目)→ 数据上报层(接口/埋点平台)→ 后台分析层(可视化/告警)
- 采集层:通过 Vue 钩子、浏览器 API 采集性能 / 错误 / 行为数据;
- 上报层:批量 + 防抖上报,避免频繁请求;
- 分析层:可选开源工具(Grafana/ELK)或第三方平台(Fundebug / 监控宝)。
二、性能监控(核心监控首屏 / 页面加载 / 接口性能)
1. 核心采集指标
- 页面性能:FP/FCP/LCP/TTI(通过
performanceAPI); - 接口性能:请求耗时 / 成功率 / 状态码;
- 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)
});
这套方案轻量化、可扩展,既可以对接自建后台,也能快速切换到第三方监控平台,核心优势是:
- 无侵入:通过全局混入、事件监听实现,不修改业务代码;
- 高性能:批量 + 防抖上报,本地缓存重试,不阻塞主流程;
- 全覆盖:性能 / 错误 / 行为维度完整,可还原问题现场。
Vue2中项目实战高频用到的设计模式有哪些
在 Vue2 项目实战中,以下是高频用到的设计模式,结合 Vue2 的特性(如选项式 API、组件化、响应式),它们能有效提升代码的可维护性和复用性:
1. 单例模式
-
核心场景:全局唯一的实例(如 Vuex Store、全局弹窗、请求拦截器)。
-
实战示例:
- Vuex 的
store是单例,全局共享状态; - 全局提示组件(如
Toast),通过Vue.prototype.$toast = new Toast()挂载为单例,避免重复创建实例。
- Vuex 的
2. 工厂模式
-
核心场景:批量创建相似的组件 / 实例,避免重复代码。
-
实战示例:
- 表单控件工厂:根据配置(如
{ type: 'input', label: '姓名' })动态生成Input、Select等表单组件; - 弹窗工厂:封装
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)。 -
实战示例:
- 表单验证策略 :定义
required、email、maxLength等验证策略,根据表单项的rules动态选择; - 支付方式策略:封装
Alipay、WechatPay等支付策略,根据用户选择切换支付逻辑。
- 表单验证策略 :定义
这些模式在 Vue2 项目中并非孤立使用(例如 "组件化 + 策略模式" 实现动态表单),结合 Vue2 的特性(如 mixin、Vuex、事件总线)能更好地落地。