在当今的前端开发中,监控系统已经不再是可选的"奢侈品",而是确保应用稳定性和用户体验的"必需品"。然而,很多团队对前端监控的理解还停留在简单的埋点统计阶段。本文将带你从零构建一个现代化的前端监控系统,涵盖错误追踪、性能监控、用户行为分析等核心功能。
为什么需要完整的前端监控?
在深入技术实现之前,我们先明确一个现代化监控系统应该解决的问题:
- 错误监控:快速发现和定位线上问题
- 性能监控:确保应用响应速度和流畅度
- 用户行为分析:理解用户如何使用你的产品
- 业务指标监控:关键业务漏斗转化率等
- 资源监控:CDN、API接口等的可用性
系统架构设计
一个完整的前端监控系统通常包含以下组件:
┌─────────────────────────────────────────┐
│ 客户端 SDK │
│ ┌─────────┬─────────┬─────────────┐ │
│ │错误监控 │性能监控 │用户行为追踪│ │
│ └─────────┴─────────┴─────────────┘ │
└─────────────────┬──────────────────────┘
│
┌─────────────────▼──────────────────────┐
│ 数据收集服务 │
│ ┌─────────────────────────────────┐ │
│ │ API接口 / WebSocket服务 │ │
│ └─────────────────────────────────┘ │
└─────────────────┬──────────────────────┘
│
┌─────────────────▼──────────────────────┐
│ 数据处理管道 │
│ ┌─────────┬─────────┬─────────────┐ │
│ │数据清洗 │数据聚合 │实时处理 │ │
│ └─────────┴─────────┴─────────────┘ │
└─────────────────┬──────────────────────┘
│
┌─────────────────▼──────────────────────┐
│ 存储与查询 │
│ ┌─────────┬─────────┬─────────────┐ │
│ │时序数据库│日志存储 │关系型数据库│ │
│ └─────────┴─────────┴─────────────┘ │
└─────────────────┬──────────────────────┘
│
┌─────────────────▼──────────────────────┐
│ 可视化与分析 │
│ ┌─────────┬─────────┬─────────────┐ │
│ │仪表盘 │告警系统 │分析报告 │ │
│ └─────────┴─────────┴─────────────┘ │
└─────────────────────────────────────────┘
核心SDK实现
1. 基础监控SDK
让我们从最基础的SDK开始实现:
class FrontendMonitor {
constructor(options = {}) {
this.config = {
appId: options.appId,
endpoint: options.endpoint || 'https://monitor.yourdomain.com/api',
sampleRate: options.sampleRate || 1.0,
maxQueueSize: options.maxQueueSize || 100,
enablePerformance: options.enablePerformance !== false,
enableError: options.enableError !== false,
enableBehavior: options.enableBehavior !== false,
};
this.queue = [];
this.isSending = false;
this.init();
}
init() {
if (this.config.enableError) {
this.initErrorMonitor();
}
if (this.config.enablePerformance) {
this.initPerformanceMonitor();
}
if (this.config.enableBehavior) {
this.initBehaviorMonitor();
}
// 页面卸载前发送剩余数据
window.addEventListener('beforeunload', () => {
this.flush();
});
// 定期发送数据
setInterval(() => this.flush(), 10000);
}
// 发送数据到服务端
send(data) {
// 采样控制
if (Math.random() > this.config.sampleRate) {
return;
}
this.queue.push({
...data,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
appId: this.config.appId,
sessionId: this.getSessionId(),
userId: this.getUserId(),
});
if (this.queue.length >= this.config.maxQueueSize) {
this.flush();
}
}
async flush() {
if (this.isSending || this.queue.length === 0) {
return;
}
this.isSending = true;
const items = [...this.queue];
this.queue = [];
try {
await fetch(this.config.endpoint + '/collect', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
items,
timestamp: Date.now(),
}),
// 使用sendBeacon兼容性更好,但为了演示使用fetch
keepalive: true,
});
} catch (error) {
// 发送失败,重新加入队列(需要去重逻辑,这里简化)
this.queue.unshift(...items);
} finally {
this.isSending = false;
}
}
getSessionId() {
let sessionId = sessionStorage.getItem('monitor_session_id');
if (!sessionId) {
sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2);
sessionStorage.setItem('monitor_session_id', sessionId);
}
return sessionId;
}
getUserId() {
// 实际项目中可以从登录信息中获取
return localStorage.getItem('user_id') || 'anonymous';
}
}
2. 错误监控实现
错误监控是前端监控中最关键的部分:
class ErrorMonitor {
constructor(sdk) {
this.sdk = sdk;
this.init();
}
init() {
// 监听全局错误
window.addEventListener('error', (event) => {
this.handleError({
type: 'js_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error?.stack,
});
}, true);
// 监听未处理的Promise rejection
window.addEventListener('unhandledrejection', (event) => {
this.handleError({
type: 'promise_error',
message: event.reason?.message || 'Unhandled Promise Rejection',
error: event.reason?.stack,
});
});
// 重写console.error
const originalConsoleError = console.error;
console.error = (...args) => {
this.handleError({
type: 'console_error',
message: args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' '),
});
originalConsoleError.apply(console, args);
};
// 监听资源加载错误
window.addEventListener('load', () => {
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
if (resource.initiatorType !== 'beacon' &&
resource.initiatorType !== 'xmlhttprequest') {
if (resource.duration === 0 || resource.transferSize === 0) {
this.handleError({
type: 'resource_error',
url: resource.name,
initiatorType: resource.initiatorType,
message: 'Resource load failed',
});
}
}
});
});
}
handleError(errorInfo) {
const errorData = {
category: 'error',
data: {
...errorInfo,
breadcrumbs: this.getBreadcrumbs(),
viewport: `${window.innerWidth}x${window.innerHeight}`,
referrer: document.referrer,
},
};
this.sdk.send(errorData);
}
getBreadcrumbs() {
// 收集用户操作轨迹,帮助复现错误
return window.__MONITOR_BREADCRUMBS__ || [];
}
// 手动捕获错误
captureError(error, extra = {}) {
this.handleError({
type: 'captured_error',
message: error.message,
error: error.stack,
...extra,
});
}
// 捕获Vue/React错误
captureFrameworkError(error, errorInfo) {
this.handleError({
type: 'framework_error',
message: error.message,
error: error.stack,
componentStack: errorInfo?.componentStack,
});
}
}
3. 性能监控实现
性能监控帮助我们了解应用的实际运行状况:
class PerformanceMonitor {
constructor(sdk) {
this.sdk = sdk;
this.init();
}
init() {
// 等待页面完全加载
if (document.readyState === 'complete') {
this.reportPerformance();
}