前端错误监控实战:从 0 到 1 搭建错误监控系统

凌晨三点,我正准备睡觉,突然收到了几十条线上报错的告警。打开监控面板一看,生产环境的某个页面报错率突然飙升到了 15%。作为技术负责人,这种时候必须立即处理。但当我查看错误日志时,却发现记录的信息少得可怜,根本无法定位具体问题。

这不是第一次遇到类似的情况了。随着公司业务的快速发展,前端项目越来越复杂,每天的 PV 已经突破百万,仅凭借控制台日志和用户反馈已经无法及时发现和解决问题。这次事件成为了导火索,我决定带领团队搭建一个完整的前端错误监控系统。

需求分析

经过和团队的深入讨论,我们梳理出了几个核心痛点。去年双十一期间,我们就吃过类似的亏。记得那天早上:

"页面打不开了!"客服部门疯狂地反馈用户投诉。 "控制台没有任何报错啊!"前端开发手足无措。 "后端接口都是正常的!"后端同学也很委屈。

最后花了整整两个小时才发现是一个第三方 SDK 的异常导致页面白屏。如果当时有完善的错误监控系统,也许十分钟就能解决问题。

监控系统设计

就像医院的体检系统,我们的错误监控也需要做到全面且精准。首先要明确监控的范围:

  1. 前端异常:
  • JavaScript 运行时错误
  • Promise 异常
  • 资源加载失败
  • API 请求异常
  • 白屏检测
  1. 性能指标:
  • 页面加载时间
  • 首屏渲染时间
  • 资源加载耗时
  • 接口响应时间
  1. 用户行为:
  • 页面访问路径
  • 点击事件追踪
  • 报错时的用户操作

架构实现

经过调研,我们设计了一个轻量级但功能完整的监控系统。就像搭建一个医疗监控系统,我们需要采集数据(体征指标),传输数据(医疗记录),分析数据(诊断报告),最后及时预警(病情告警)。

首先是错误采集层。我们通过重写浏览器的原生方法来捕获各类错误:

class ErrorCatcher {
  constructor() {
    // 捕获全局错误
    window.addEventListener('error', this.handleError.bind(this), true)
    // 捕获 Promise 异常
    window.addEventListener('unhandledrejection', this.handlePromiseError.bind(this), true)
    // 重写 console.error
    this.wrapConsoleError()
  }

  handleError(event) {
    const errorInfo = {
      type: 'javascript',
      message: event.message,
      filename: event.filename,
      position: `${event.lineno}:${event.colno}`,
      stack: event.error?.stack,
      // 收集用户信息和环境信息
      userAgent: navigator.userAgent,
      timestamp: new Date().getTime(),
      url: window.location.href,
      // 获取最近的用户行为
      userActions: this.getUserActions()
    }

    this.report(errorInfo)
  }

  getUserActions() {
    // 返回最近的用户操作记录
    return this.actionTracker.getRecentActions()
  }
}

然后是数据上报服务。考虑到错误发生时网络可能不稳定,我们实现了一个可靠的上报机制:

class Reporter {
  constructor() {
    this.queue = []
    this.retryTimes = 3
    this.batchSize = 10
    // 使用 IndexedDB 做离线存储
    this.initStorage()
  }

  async report(data) {
    try {
      // 优先使用 sendBeacon,它更可靠
      if (navigator.sendBeacon) {
        const success = navigator.sendBeacon('/api/errors', JSON.stringify(data))
        if (success) return
      }

      // 降级使用 fetch
      await this.sendWithRetry(data)
    } catch (error) {
      // 存储到本地
      await this.saveToIndexedDB(data)
    }
  }

  async sendWithRetry(data, times = 0) {
    try {
      const response = await fetch('/api/errors', {
        method: 'POST',
        body: JSON.stringify(data)
      })
      if (!response.ok) throw new Error('上报失败')
    } catch (error) {
      if (times < this.retryTimes) {
        // 延迟重试,避免网络风暴
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, times)))
        return this.sendWithRetry(data, times + 1)
      }
      throw error
    }
  }
}

为了更好地分析问题,我们还加入了用户行为追踪:

class ActionTracker {
  constructor() {
    this.actions = []
    this.maxActions = 50
    this.initTrackers()
  }

  initTrackers() {
    // 记录点击事件
    document.addEventListener(
      'click',
      event => {
        const target = event.target
        this.addAction({
          type: 'click',
          element: this.getElementPath(target),
          timestamp: new Date().getTime()
        })
      },
      true
    )

    // 记录路由变化
    if (window.history) {
      const oldPushState = window.history.pushState
      window.history.pushState = (...args) => {
        oldPushState.apply(window.history, args)
        this.addAction({
          type: 'navigation',
          to: window.location.pathname,
          timestamp: new Date().getTime()
        })
      }
    }
  }

  getElementPath(element) {
    // 生成元素的唯一路径
    const path = []
    while (element && element.nodeType === Node.ELEMENT_NODE) {
      let selector = element.nodeName.toLowerCase()
      if (element.id) {
        selector += `#${element.id}`
        path.unshift(selector)
        break
      }
      path.unshift(selector)
      element = element.parentNode
    }
    return path.join(' > ')
  }
}

数据分析和告警

收集到数据后,最关键的是如何分析和利用这些数据。我们建立了一个实时分析系统,它能够:

  • 自动对错误进行分类和聚合
  • 计算各类错误的发生频率和影响范围
  • 识别异常的错误波峰
  • 追踪错误的来源和传播路径

当发现异常时,系统会通过多个渠道及时通知相关人员:

  • 钉钉群机器人发送告警消息
  • 邮件通知详细的错误报告
  • 严重问题时自动发起语音通话

实践效果

系统上线后,我们很快就尝到了甜头。有一次,系统在凌晨检测到了一个第三方 SDK 的异常,在影响到大量用户之前,我们就及时进行了降级处理。

最让我印象深刻的是双十一当天。去年我们手忙脚乱了一整天,而今年,通过监控系统我们精准定位到了几个性能瓶颈,提前进行了优化,整个活动过程异常平稳。

经验总结

建设监控系 统的过程中,我们学到了很多:

监控要全面但要有重点,就像体检要查全身,但对重要器官要特别关注。错误信息要详细但不能冗余,就像病历要记录关键症状,但不需要记录病人吃早餐吃了什么。最重要的是要建立快速响应机制,就像急诊科要确保随时有医生值班。

写在最后

前端错误监控就像是应用的健康检查系统,它不能保证不出问题,但能帮助我们更快地发现和解决问题。正如那句老话说的:"预防胜于治疗",一个好的监控系统就是最好的预防。

有什么问题欢迎在评论区讨论,让我们一起探讨前端监控的最佳实践!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值