Skip to content

前端监控与可观测性建设 ​

前端最被动的时刻,是用户反馈"页面白屏了",而团队没有任何数据:不知道影响多少人、从哪个版本开始、错误堆栈指向哪里。

监控不是装一个 SDK 就结束。完整的可观测性要覆盖错误、性能、行为和接口,并且能把数据关联到版本、用户和具体代码。

监控体系的四个维度 ​

维度关注问题典型数据
错误哪里坏了,影响多少人JS 异常、资源加载失败、接口错误、框架错误
性能用户是否觉得慢LCP、INP、CLS、TTFB、自定义耗时
行为用户做了什么页面访问、点击路径、关键操作
接口请求是否可靠成功率、耗时分布、状态码、超时

四个维度要能通过同一个会话 ID 关联。否则看到错误也不知道对应哪次访问、哪个接口、哪个版本。

错误采集 ​

前端错误来源比想象中多,需要分别处理:

全局 JavaScript 异常 ​

ts
window.addEventListener('error', (event) => {
  report({
    type: 'js-error',
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error?.stack,
    release: import.meta.env.VITE_APP_VERSION
  })
})

Promise 未捕获异常 ​

ts
window.addEventListener('unhandledrejection', (event) => {
  report({
    type: 'unhandled-rejection',
    reason: normalizeError(event.reason),
    release: import.meta.env.VITE_APP_VERSION
  })
})

资源加载失败 ​

资源错误不会冒泡,但会在捕获阶段触发 error,需要判断事件目标:

ts
window.addEventListener(
  'error',
  (event) => {
    const target = event.target as HTMLElement | null
    if (!(target instanceof HTMLScriptElement) &&
        !(target instanceof HTMLLinkElement) &&
        !(target instanceof HTMLImageElement)) {
      return
    }

    report({
      type: 'resource-error',
      tagName: target.tagName,
      url: target instanceof HTMLImageElement ? target.src : target.getAttribute('href') ?? target.getAttribute('src')
    })
  },
  true
)

框架错误 ​

Vue 的组件错误默认不会抛到 window.onerror,需要注册框架级处理器:

ts
app.config.errorHandler = (error, instance, info) => {
  report({
    type: 'vue-error',
    error: normalizeError(error),
    info,
    component: instance?.$options.name
  })
}

React 侧使用 Error Boundary 捕获渲染错误,同时保留 window.onerror 作为兜底。异步事件和 Promise 中的错误仍需全局监听。

性能指标采集 ​

Core Web Vitals 是基础,但业务还需要自定义指标,例如编辑器首屏渲染、搜索结果返回、支付流程耗时:

ts
performance.mark('checkout:start')

await submitOrder()

performance.mark('checkout:end')
performance.measure('checkout:total', 'checkout:start', 'checkout:end')

const [measure] = performance.getEntriesByName('checkout:total')
report({
  type: 'custom-metric',
  name: 'checkout:total',
  value: measure.duration
})

注意自定义指标要带上下文:页面、设备、网络、版本、用户分群。同样的 800ms,在桌面和低端安卓上的含义完全不同。

接入 Sentry ​

自建采集链路成本不低。Sentry 这类平台提供了错误聚合、Source Map 还原、性能追踪和会话回放:

ts
import * as Sentry from '@sentry/vue'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'

const app = createApp(App)
const router = createRouter({
  history: createWebHistory(),
  routes: []
})

Sentry.init({
  app,
  dsn: import.meta.env.VITE_SENTRY_DSN,
  environment: import.meta.env.MODE,
  release: import.meta.env.VITE_APP_VERSION,
  integrations: [Sentry.browserTracingIntegration({ router })],
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.01,
  replaysOnErrorSampleRate: 1
})

app.use(router)
app.mount('#app')

关键配置:

  • tracesSampleRate:性能追踪采样率。生产环境通常 5% 到 20%,全量成本高。
  • replaysOnErrorSampleRate: 1:错误会话完整录制,便于复现,但要注意隐私脱敏。
  • release:每次发布使用唯一版本号,才能判断错误是否由新版本引入。

Source Map 是定位问题的前提 ​

压缩后的堆栈 a.b.c is not a function 没有价值。构建时生成 Source Map 并上传到监控平台,同时不要公开暴露:

ts
// vite.config.ts
import { sentryVitePlugin } from '@sentry/vite-plugin'

export default defineConfig({
  build: {
    sourcemap: true
  },
  plugins: [
    sentryVitePlugin({
      org: process.env.SENTRY_ORG,
      project: process.env.SENTRY_PROJECT,
      authToken: process.env.SENTRY_AUTH_TOKEN,
      sourcemaps: {
        assets: './dist/**'
      }
    })
  ]
})

上传后可以把构建产物中的 .map 文件删除,避免源码泄漏。Sentry 通过 sourceMappingURL 和 release 关联堆栈。

上报本身的工程问题 ​

监控 SDK 不能成为新的故障源:

  • 自动兜底:上报代码整体包在 try/catch 中,失败静默。
  • 采样与限流:同一错误在短时间内只上报一次,避免循环报错打爆后端。
  • 离线补发:将事件暂存,页面卸载时使用 sendBeacon 发送。
  • 隐私保护:不采集密码、身份证、银行卡等敏感信息;会话回放要遮罩输入框。
  • 体积控制:SDK 按需加载,避免阻塞首屏。
ts
function safeReport(payload: unknown) {
  try {
    const body = JSON.stringify(payload)
    if (body.length > 32_000) return

    if (navigator.sendBeacon) {
      navigator.sendBeacon('/api/telemetry', new Blob([body], { type: 'application/json' }))
      return
    }

    fetch('/api/telemetry', {
      method: 'POST',
      body,
      keepalive: true,
      headers: { 'Content-Type': 'application/json' }
    }).catch(() => {})
  } catch {
    // 监控失败不能影响业务
  }
}

从数据到告警闭环 ​

采集只是起点,真正产生价值的是闭环:

  1. 按版本对比:新版本错误率是否上升,能否自动关联到发布记录。
  2. 按影响面排序:错误数多不等于优先级高,要看影响用户数、核心页面和转化路径。
  3. 告警分级:白屏、支付失败等 P0 实时告警;偶发 JS 错误进入日报。
  4. 接入工单:告警自动创建任务,带上错误详情、复现步骤和版本号。
  5. 回滚机制:错误率超过阈值时能快速回滚,而不是先定位半小时。

常见坑 ​

  1. 只采集 window.onerror。漏掉 Promise、资源和框架错误,覆盖率不足。
  2. 没有 Source Map。错误堆栈无法定位,采集了也没法修。
  3. 全量上报。高流量下成本失控,也会影响页面性能。
  4. 忽略监控 SDK 的副作用。SDK 体积、初始化时间和请求竞争都会影响 CWV。
  5. 数据没有 owner。看板没人看、告警没人处理,监控形同虚设。

小结 ​

前端可观测性的核心是让问题可关联:错误关联到版本、用户、请求和操作路径,性能关联到页面和发布,行为关联到会话。工具可以买,但采集规范、告警分级和修复闭环必须由团队自己建立。

基于 VitePress 构建 · 持续更新中