前端监控包含哪些方面?如何实现错误监控?

前端监控三大领域

  1. 错误监控:JS 错误、资源加载失败、API 错误
  2. 性能监控:加载时间、FCP、LCP、白屏时间
  3. 行为监控:PV/UV、点击流、用户路径

错误监控

1. JS 运行时错误

window.addEventListener('error', (event) => {
  report({
    type: 'js_error',
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error?.stack,
    timestamp: Date.now()
  })
}, true)

2. Promise 未捕获异常

window.addEventListener('unhandledrejection', (event) => {
  report({
    type: 'promise_error',
    reason: event.reason?.message || event.reason,
    stack: event.reason?.stack,
    timestamp: Date.now()
  })
})

3. 资源加载错误

window.addEventListener('error', (event) => {
  const target = event.target
  if (target && (target.tagName === 'IMG' || target.tagName === 'SCRIPT' || target.tagName === 'LINK')) {
    report({
      type: 'resource_error',
      tagName: target.tagName,
      src: target.src || target.href,
      timestamp: Date.now()
    })
  }
}, true)  // 必须用捕获阶段

4. 接口错误

// 拦截 fetch
const originalFetch = window.fetch
window.fetch = async (...args) => {
  const [url, options] = args
  const startTime = Date.now()
  try {
    const response = await originalFetch(...args)
    if (!response.ok) {
      report({
        type: 'api_error',
        url: typeof url === 'string' ? url : url.url,
        status: response.status,
        duration: Date.now() - startTime
      })
    }
    return response
  } catch (error) {
    report({
      type: 'api_error',
      url: typeof url === 'string' ? url : url.url,
      error: error.message,
      duration: Date.now() - startTime
    })
    throw error
  }
}

// 拦截 XMLHttpRequest
const originalOpen = XMLHttpRequest.prototype.open
const originalSend = XMLHttpRequest.prototype.send
XMLHttpRequest.prototype.open = function(method, url, ...args) {
  this._url = url
  this._method = method
  this._startTime = Date.now()
  originalOpen.call(this, method, url, ...args)
}
XMLHttpRequest.prototype.send = function(...args) {
  this.addEventListener('loadend', () => {
    if (this.status >= 400) {
      report({
        type: 'api_error',
        url: this._url,
        method: this._method,
        status: this.status,
        duration: Date.now() - this._startTime
      })
    }
  })
  originalSend.call(this, ...args)
}

性能监控

// Web Vitals
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals'

onCLS(console.log)   // 累积布局偏移
onFID(console.log)   // 首次输入延迟
onLCP(console.log)   // 最大内容绘制
onFCP(console.log)   // 首次内容绘制
onTTFB(console.log)  // 首字节时间

// Performance API
const timing = performance.getEntriesByType('navigation')[0]
console.log({
  dns: timing.domainLookupEnd - timing.domainLookupStart,
  tcp: timing.connectEnd - timing.connectStart,
  ttfb: timing.responseStart - timing.requestStart,
  domReady: timing.domComplete - timing.domLoading,
  load: timing.loadEventEnd - timing.fetchStart
})

上报方案

function report(data) {
  data.userAgent = navigator.userAgent
  data.url = location.href
  data.userId = getUserId()
  
  // 方案 1:sendBeacon(推荐,页面卸载时也能发送)
  if (navigator.sendBeacon) {
    const blob = new Blob([JSON.stringify(data)], { type: 'application/json' })
    navigator.sendBeacon('/api/monitor', blob)
    return
  }
  
  // 方案 2:fetch keepalive
  fetch('/api/monitor', {
    method: 'POST',
    body: JSON.stringify(data),
    keepalive: true
  })
  
  // 方案 3:Image(简单但只支持 GET)
  new Image().src = `/api/monitor?data=${encodeURIComponent(JSON.stringify(data))}`
}

Source Map 还原

生产环境代码经过压缩混淆,错误堆栈不可读:

// 服务器端使用 source-map 库还原
const sourceMap = require('source-map')
const fs = require('fs')

async function还原(line, column) {
  const rawSourceMap = JSON.parse(fs.readFileSync('app.js.map', 'utf8'))
  const consumer = await new sourceMap.SourceMapConsumer(rawSourceMap)
  const original = consumer.originalPositionFor({ line, column })
  console.log(original.source, original.line, original.name)
}

常用监控工具

工具 特点
Sentry 错误监控、Source Map
LogRocket 录屏回放
Google Analytics 行为分析
阿里云 ARMS 全链路监控
Datadog 全栈监控

监控指标

同分类其他题目