从零构建一个现代化的前端监控系统:不只是埋点那么简单

6 阅读1分钟

在当今的前端开发中,监控系统已经不再是可选的"奢侈品",而是确保应用稳定性和用户体验的"必需品"。然而,很多团队对前端监控的理解还停留在简单的埋点统计阶段。本文将带你从零构建一个现代化的前端监控系统,涵盖错误追踪、性能监控、用户行为分析等核心功能。

为什么需要完整的前端监控?

在深入技术实现之前,我们先明确一个现代化监控系统应该解决的问题:

  1. 错误监控:快速发现和定位线上问题
  2. 性能监控:确保应用响应速度和流畅度
  3. 用户行为分析:理解用户如何使用你的产品
  4. 业务指标监控:关键业务漏斗转化率等
  5. 资源监控: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();
    }