Skip to content

前端监控分析完全指南

前端监控是保障线上应用稳定运行的重要手段。通过全面的监控体系,可以及时发现问题、分析用户行为、优化产品体验。

🎯 监控体系架构

监控分类

mermaid
graph TB
    A[前端监控] --> B[错误监控]
    A --> C[性能监控]
    A --> D[用户行为监控]
    A --> E[业务监控]
    
    B --> B1[JavaScript错误]
    B --> B2[资源加载错误]
    B --> B3[接口错误]
    
    C --> C1[页面性能]
    C --> C2[资源性能]
    C --> C3[运行时性能]
    
    D --> D1[页面访问]
    D --> D2[用户操作]
    D --> D3[用户路径]
    
    E --> E1[核心指标]
    E --> E2[转化率]
    E --> E3[留存率]

监控指标体系

监控类型核心指标监控目标告警阈值
错误监控错误率、错误数量系统稳定性错误率 > 1%
性能监控LCP、FID、CLS用户体验LCP > 2.5s
可用性监控可用率、响应时间服务可用性可用率 < 99.9%
业务监控PV、UV、转化率业务健康度转化率下降 > 10%

🚨 错误监控

JavaScript 错误捕获

1. 全局错误监控

javascript
// 错误监控类
class ErrorMonitor {
  constructor(options = {}) {
    this.apiUrl = options.apiUrl || '/api/errors'
    this.userId = options.userId
    this.version = options.version
    this.environment = options.environment || 'production'
    
    this.init()
  }
  
  init() {
    // 捕获 JavaScript 运行时错误
    window.addEventListener('error', this.handleError.bind(this))
    
    // 捕获 Promise 未处理的 rejection
    window.addEventListener('unhandledrejection', this.handlePromiseRejection.bind(this))
    
    // 捕获资源加载错误
    window.addEventListener('error', this.handleResourceError.bind(this), true)
  }
  
  handleError(event) {
    const errorInfo = {
      type: 'javascript',
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
      stack: event.error?.stack,
      userAgent: navigator.userAgent,
      url: window.location.href,
      timestamp: Date.now(),
      userId: this.userId,
      version: this.version,
      environment: this.environment
    }
    
    this.reportError(errorInfo)
  }
  
  handlePromiseRejection(event) {
    const errorInfo = {
      type: 'promise',
      message: event.reason?.message || 'Unhandled Promise Rejection',
      stack: event.reason?.stack,
      userAgent: navigator.userAgent,
      url: window.location.href,
      timestamp: Date.now(),
      userId: this.userId,
      version: this.version,
      environment: this.environment
    }
    
    this.reportError(errorInfo)
  }
  
  handleResourceError(event) {
    if (event.target !== window) {
      const errorInfo = {
        type: 'resource',
        message: 'Resource loading failed',
        resourceUrl: event.target.src || event.target.href,
        tagName: event.target.tagName,
        url: window.location.href,
        timestamp: Date.now(),
        userId: this.userId,
        version: this.version,
        environment: this.environment
      }
      
      this.reportError(errorInfo)
    }
  }
  
  reportError(errorInfo) {
    // 错误去重
    const errorKey = `${errorInfo.type}-${errorInfo.message}-${errorInfo.filename}-${errorInfo.lineno}`
    if (this.reportedErrors?.has(errorKey)) {
      return
    }
    
    if (!this.reportedErrors) {
      this.reportedErrors = new Set()
    }
    this.reportedErrors.add(errorKey)
    
    // 发送错误报告
    fetch(this.apiUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorInfo)
    }).catch(err => {
      console.error('Failed to report error:', err)
    })
  }
  
  // 手动报告错误
  captureException(error, context = {}) {
    const errorInfo = {
      type: 'manual',
      message: error.message,
      stack: error.stack,
      context,
      userAgent: navigator.userAgent,
      url: window.location.href,
      timestamp: Date.now(),
      userId: this.userId,
      version: this.version,
      environment: this.environment
    }
    
    this.reportError(errorInfo)
  }
}

// 初始化错误监控
const errorMonitor = new ErrorMonitor({
  apiUrl: '/api/errors',
  userId: getCurrentUserId(),
  version: '1.0.0',
  environment: process.env.NODE_ENV
})

// 手动捕获错误
try {
  riskyOperation()
} catch (error) {
  errorMonitor.captureException(error, { operation: 'riskyOperation' })
}

错误边界组件

React 错误边界

javascript
// ErrorBoundary.jsx
import React from 'react'

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null, errorInfo: null }
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true }
  }
  
  componentDidCatch(error, errorInfo) {
    this.setState({
      error,
      errorInfo
    })
    
    // 报告错误到监控系统
    window.errorMonitor?.captureException(error, {
      componentStack: errorInfo.componentStack,
      errorBoundary: this.constructor.name
    })
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Something went wrong.</h2>
          {process.env.NODE_ENV === 'development' && (
            <details style={{ whiteSpace: 'pre-wrap' }}>
              <summary>Error details</summary>
              {this.state.error && this.state.error.toString()}
              <br />
              {this.state.errorInfo.componentStack}
            </details>
          )}
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      )
    }
    
    return this.props.children
  }
}

// 使用错误边界
function App() {
  return (
    <ErrorBoundary>
      <Header />
      <Main />
      <Footer />
    </ErrorBoundary>
  )
}

Vue 错误处理

javascript
// Vue 3 全局错误处理
const app = createApp(App)

app.config.errorHandler = (err, instance, info) => {
  console.error('Vue error:', err)
  console.error('Component instance:', instance)
  console.error('Error info:', info)
  
  // 报告错误
  window.errorMonitor?.captureException(err, {
    componentName: instance?.$options.name,
    errorInfo: info
  })
}

// Vue 2 错误处理
Vue.config.errorHandler = function (err, vm, info) {
  console.error('Vue error:', err)
  
  window.errorMonitor?.captureException(err, {
    componentName: vm?.$options.name,
    errorInfo: info
  })
}

📈 性能监控

实时性能监控

javascript
// 性能监控类
class PerformanceMonitor {
  constructor(options = {}) {
    this.apiUrl = options.apiUrl || '/api/performance'
    this.sampleRate = options.sampleRate || 0.1 // 10% 采样率
    this.metrics = new Map()
    
    this.init()
  }
  
  init() {
    // 监控页面性能
    this.observePagePerformance()
    
    // 监控资源性能
    this.observeResourcePerformance()
    
    // 监控长任务
    this.observeLongTasks()
    
    // 监控内存使用
    this.observeMemoryUsage()
  }
  
  observePagePerformance() {
    // 使用 PerformanceObserver 监控
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.entryType === 'navigation') {
          this.reportPagePerformance(entry)
        }
      }
    })
    
    observer.observe({ entryTypes: ['navigation'] })
  }
  
  observeResourcePerformance() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.transferSize > 100000) { // 大于100KB
          this.reportLargeResource(entry)
        }
      }
    })
    
    observer.observe({ entryTypes: ['resource'] })
  }
  
  observeLongTasks() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.duration > 50) { // 长任务阈值50ms
            this.reportLongTask(entry)
          }
        }
      })
      
      observer.observe({ entryTypes: ['longtask'] })
    }
  }
  
  observeMemoryUsage() {
    if ('memory' in performance) {
      setInterval(() => {
        const memory = performance.memory
        this.reportMemoryUsage({
          usedJSHeapSize: memory.usedJSHeapSize,
          totalJSHeapSize: memory.totalJSHeapSize,
          jsHeapSizeLimit: memory.jsHeapSizeLimit
        })
      }, 30000) // 每30秒检查一次
    }
  }
  
  reportPagePerformance(entry) {
    if (Math.random() > this.sampleRate) return
    
    const performanceData = {
      type: 'page-performance',
      metrics: {
        domContentLoaded: entry.domContentLoadedEventEnd - entry.domContentLoadedEventStart,
        loadComplete: entry.loadEventEnd - entry.loadEventStart,
        firstByte: entry.responseStart - entry.requestStart,
        domInteractive: entry.domInteractive - entry.domLoading,
        domComplete: entry.domComplete - entry.domLoading
      },
      url: window.location.href,
      timestamp: Date.now()
    }
    
    this.sendMetrics(performanceData)
  }
  
  sendMetrics(data) {
    fetch(this.apiUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    }).catch(err => {
      console.error('Failed to send performance metrics:', err)
    })
  }
}

// 初始化性能监控
const performanceMonitor = new PerformanceMonitor({
  apiUrl: '/api/performance',
  sampleRate: 0.1
})

👥 用户行为监控

用户行为追踪

javascript
// 用户行为监控类
class UserBehaviorTracker {
  constructor(options = {}) {
    this.apiUrl = options.apiUrl || '/api/analytics'
    this.sessionId = this.generateSessionId()
    this.userId = options.userId
    this.events = []
    this.batchSize = options.batchSize || 10
    
    this.init()
  }
  
  init() {
    // 页面访问追踪
    this.trackPageView()
    
    // 点击事件追踪
    this.trackClicks()
    
    // 滚动行为追踪
    this.trackScrolling()
    
    // 表单交互追踪
    this.trackFormInteractions()
    
    // 页面停留时间
    this.trackPageDuration()
  }
  
  trackPageView() {
    const pageData = {
      type: 'page_view',
      url: window.location.href,
      title: document.title,
      referrer: document.referrer,
      userAgent: navigator.userAgent,
      timestamp: Date.now(),
      sessionId: this.sessionId,
      userId: this.userId
    }
    
    this.addEvent(pageData)
  }
  
  trackClicks() {
    document.addEventListener('click', (event) => {
      const target = event.target
      const clickData = {
        type: 'click',
        element: target.tagName.toLowerCase(),
        className: target.className,
        id: target.id,
        text: target.textContent?.slice(0, 100),
        xpath: this.getXPath(target),
        timestamp: Date.now(),
        sessionId: this.sessionId,
        userId: this.userId
      }
      
      this.addEvent(clickData)
    })
  }
  
  trackScrolling() {
    let maxScrollDepth = 0
    
    const throttledScroll = this.throttle(() => {
      const scrollDepth = Math.round(
        (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100
      )
      
      if (scrollDepth > maxScrollDepth) {
        maxScrollDepth = scrollDepth
        
        this.addEvent({
          type: 'scroll',
          depth: scrollDepth,
          timestamp: Date.now(),
          sessionId: this.sessionId,
          userId: this.userId
        })
      }
    }, 1000)
    
    window.addEventListener('scroll', throttledScroll)
  }
  
  trackFormInteractions() {
    document.addEventListener('submit', (event) => {
      const form = event.target
      const formData = new FormData(form)
      const fields = Array.from(formData.keys())
      
      this.addEvent({
        type: 'form_submit',
        formId: form.id,
        formClass: form.className,
        fieldCount: fields.length,
        fields: fields,
        timestamp: Date.now(),
        sessionId: this.sessionId,
        userId: this.userId
      })
    })
  }
  
  addEvent(eventData) {
    this.events.push(eventData)
    
    if (this.events.length >= this.batchSize) {
      this.sendEvents()
    }
  }
  
  sendEvents() {
    if (this.events.length === 0) return
    
    const eventsToSend = [...this.events]
    this.events = []
    
    fetch(this.apiUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ events: eventsToSend })
    }).catch(err => {
      console.error('Failed to send analytics events:', err)
      // 失败时重新加入队列
      this.events.unshift(...eventsToSend)
    })
  }
  
  // 工具方法
  generateSessionId() {
    return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now()
  }
  
  getXPath(element) {
    if (element.id) return `//*[@id="${element.id}"]`
    
    const parts = []
    while (element && element.nodeType === Node.ELEMENT_NODE) {
      let index = 0
      let sibling = element.previousSibling
      
      while (sibling) {
        if (sibling.nodeType === Node.ELEMENT_NODE && sibling.tagName === element.tagName) {
          index++
        }
        sibling = sibling.previousSibling
      }
      
      const tagName = element.tagName.toLowerCase()
      const pathIndex = index > 0 ? `[${index + 1}]` : ''
      parts.unshift(tagName + pathIndex)
      
      element = element.parentNode
    }
    
    return parts.length ? '/' + parts.join('/') : ''
  }
  
  throttle(func, limit) {
    let inThrottle
    return function(...args) {
      if (!inThrottle) {
        func.apply(this, args)
        inThrottle = true
        setTimeout(() => inThrottle = false, limit)
      }
    }
  }
}

// 初始化用户行为追踪
const behaviorTracker = new UserBehaviorTracker({
  apiUrl: '/api/analytics',
  userId: getCurrentUserId(),
  batchSize: 10
})

热力图分析

javascript
// 热力图数据收集
class HeatmapTracker {
  constructor(options = {}) {
    this.apiUrl = options.apiUrl || '/api/heatmap'
    this.sampleRate = options.sampleRate || 0.05 // 5% 采样率
    this.clickData = []
    
    this.init()
  }
  
  init() {
    if (Math.random() > this.sampleRate) return
    
    document.addEventListener('click', this.trackClick.bind(this))
    document.addEventListener('mousemove', this.throttle(this.trackMouseMove.bind(this), 100))
  }
  
  trackClick(event) {
    const clickInfo = {
      type: 'click',
      x: event.clientX,
      y: event.clientY,
      pageX: event.pageX,
      pageY: event.pageY,
      element: event.target.tagName,
      className: event.target.className,
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight
      },
      timestamp: Date.now()
    }
    
    this.clickData.push(clickInfo)
    
    if (this.clickData.length >= 50) {
      this.sendHeatmapData()
    }
  }
  
  trackMouseMove(event) {
    // 记录鼠标移动轨迹(采样)
    if (Math.random() < 0.01) { // 1% 采样率
      this.clickData.push({
        type: 'mousemove',
        x: event.clientX,
        y: event.clientY,
        timestamp: Date.now()
      })
    }
  }
  
  sendHeatmapData() {
    if (this.clickData.length === 0) return
    
    const dataToSend = [...this.clickData]
    this.clickData = []
    
    fetch(this.apiUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        url: window.location.href,
        data: dataToSend
      })
    }).catch(err => {
      console.error('Failed to send heatmap data:', err)
    })
  }
  
  throttle(func, limit) {
    let inThrottle
    return function(...args) {
      if (!inThrottle) {
        func.apply(this, args)
        inThrottle = true
        setTimeout(() => inThrottle = false, limit)
      }
    }
  }
}

📊 业务监控

关键业务指标追踪

javascript
// 业务监控类
class BusinessMetricsTracker {
  constructor(options = {}) {
    this.apiUrl = options.apiUrl || '/api/business-metrics'
    this.userId = options.userId
    
    this.init()
  }
  
  init() {
    // 监控页面停留时间
    this.trackPageDuration()
    
    // 监控转化漏斗
    this.trackConversionFunnel()
    
    // 监控核心业务操作
    this.trackBusinessActions()
  }
  
  trackPageDuration() {
    const startTime = Date.now()
    
    const sendDuration = () => {
      const duration = Date.now() - startTime
      
      this.sendMetric({
        type: 'page_duration',
        duration,
        url: window.location.href,
        userId: this.userId,
        timestamp: Date.now()
      })
    }
    
    // 页面卸载时发送
    window.addEventListener('beforeunload', sendDuration)
    
    // 页面隐藏时发送
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        sendDuration()
      }
    })
  }
  
  trackConversionFunnel() {
    const funnelSteps = [
      'landing',
      'product_view',
      'add_to_cart',
      'checkout',
      'payment',
      'success'
    ]
    
    window.trackFunnelStep = (step, data = {}) => {
      if (funnelSteps.includes(step)) {
        this.sendMetric({
          type: 'funnel_step',
          step,
          data,
          userId: this.userId,
          sessionId: this.sessionId,
          timestamp: Date.now()
        })
      }
    }
  }
  
  trackBusinessActions() {
    // 监控关键业务操作
    window.trackBusinessAction = (action, data = {}) => {
      this.sendMetric({
        type: 'business_action',
        action,
        data,
        userId: this.userId,
        url: window.location.href,
        timestamp: Date.now()
      })
    }
  }
  
  sendMetric(metricData) {
    fetch(this.apiUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(metricData)
    }).catch(err => {
      console.error('Failed to send business metric:', err)
    })
  }
}

// 使用示例
const businessTracker = new BusinessMetricsTracker({
  apiUrl: '/api/business-metrics',
  userId: getCurrentUserId()
})

// 在关键业务节点调用
function addToCart(product) {
  // 业务逻辑
  addProductToCart(product)
  
  // 追踪业务指标
  window.trackBusinessAction('add_to_cart', {
    productId: product.id,
    price: product.price,
    category: product.category
  })
}

🔔 告警系统

实时告警配置

javascript
// 告警规则配置
const alertRules = {
  errorRate: {
    threshold: 0.01, // 1%
    window: 300,     // 5分钟窗口
    action: 'immediate'
  },
  performanceScore: {
    threshold: 70,
    window: 600,     // 10分钟窗口
    action: 'delayed'
  },
  availability: {
    threshold: 0.999, // 99.9%
    window: 900,      // 15分钟窗口
    action: 'immediate'
  }
}

// 告警处理
class AlertManager {
  constructor(rules) {
    this.rules = rules
    this.alertHistory = new Map()
  }
  
  checkMetric(metricName, value, timestamp) {
    const rule = this.rules[metricName]
    if (!rule) return
    
    const shouldAlert = this.evaluateRule(rule, value, timestamp)
    
    if (shouldAlert && !this.isAlertSuppressed(metricName)) {
      this.sendAlert(metricName, value, rule)
      this.recordAlert(metricName, timestamp)
    }
  }
  
  evaluateRule(rule, value, timestamp) {
    switch (rule.action) {
      case 'immediate':
        return value > rule.threshold
      case 'delayed':
        return this.checkWindowAverage(rule, value, timestamp)
      default:
        return false
    }
  }
  
  sendAlert(metricName, value, rule) {
    const alertData = {
      metric: metricName,
      value,
      threshold: rule.threshold,
      severity: this.getSeverity(metricName),
      timestamp: Date.now(),
      environment: process.env.NODE_ENV
    }
    
    // 发送到告警系统
    fetch('/api/alerts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(alertData)
    })
    
    // 发送到 Slack
    this.sendSlackAlert(alertData)
  }
  
  sendSlackAlert(alertData) {
    const message = {
      text: `🚨 Alert: ${alertData.metric}`,
      attachments: [{
        color: 'danger',
        fields: [
          { title: 'Metric', value: alertData.metric, short: true },
          { title: 'Value', value: alertData.value, short: true },
          { title: 'Threshold', value: alertData.threshold, short: true },
          { title: 'Environment', value: alertData.environment, short: true }
        ]
      }]
    }
    
    fetch(process.env.SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(message)
    })
  }
}

📊 数据分析和可视化

监控仪表板

javascript
// 实时监控仪表板
class MonitoringDashboard {
  constructor() {
    this.metrics = new Map()
    this.charts = new Map()
    
    this.init()
  }
  
  init() {
    this.setupWebSocket()
    this.createCharts()
    this.startDataRefresh()
  }
  
  setupWebSocket() {
    this.ws = new WebSocket('wss://monitoring.example.com/realtime')
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      this.updateMetrics(data)
    }
  }
  
  updateMetrics(data) {
    this.metrics.set(data.metric, data.value)
    this.updateChart(data.metric, data.value, data.timestamp)
    
    // 检查告警条件
    this.checkAlerts(data.metric, data.value)
  }
  
  createCharts() {
    // 错误率图表
    this.charts.set('errorRate', new Chart('errorRateChart', {
      type: 'line',
      data: {
        labels: [],
        datasets: [{
          label: 'Error Rate (%)',
          data: [],
          borderColor: 'rgb(255, 99, 132)',
          tension: 0.1
        }]
      },
      options: {
        responsive: true,
        scales: {
          y: {
            beginAtZero: true,
            max: 5
          }
        }
      }
    }))
    
    // 性能指标图表
    this.charts.set('performance', new Chart('performanceChart', {
      type: 'bar',
      data: {
        labels: ['LCP', 'FID', 'CLS'],
        datasets: [{
          label: 'Performance Metrics',
          data: [0, 0, 0],
          backgroundColor: [
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)',
            'rgba(255, 205, 86, 0.2)'
          ]
        }]
      }
    }))
  }
}

💡 最佳实践

1. 监控策略

  • 分层监控:从基础设施到业务层面的全面监控
  • 主动监控:预防性监控,而非被动响应
  • 智能告警:避免告警疲劳,设置合理的告警阈值
  • 数据驱动:基于监控数据进行决策和优化

2. 性能预算

javascript
// 性能预算配置
const performanceBudget = {
  // 时间预算
  timing: {
    FCP: 1800,  // 首次内容绘制 < 1.8s
    LCP: 2500,  // 最大内容绘制 < 2.5s
    FID: 100,   // 首次输入延迟 < 100ms
    CLS: 0.1    // 累积布局偏移 < 0.1
  },
  
  // 资源预算
  resources: {
    javascript: 200 * 1024,  // JS 总大小 < 200KB
    css: 50 * 1024,          // CSS 总大小 < 50KB
    images: 500 * 1024,      // 图片总大小 < 500KB
    fonts: 100 * 1024        // 字体总大小 < 100KB
  }
}

3. 隐私保护

  • 数据脱敏:敏感信息不记录或脱敏处理
  • 用户同意:遵循 GDPR 等隐私法规
  • 数据最小化:只收集必要的数据
  • 安全传输:使用 HTTPS 传输监控数据

4. 监控成本控制

  • 采样策略:合理设置采样率
  • 数据压缩:压缩传输的监控数据
  • 批量发送:批量发送减少网络请求
  • 存储优化:设置合理的数据保留期

通过建立完善的监控分析体系,可以及时发现和解决问题,持续优化用户体验,提升产品质量。