前端监控
概述
前端监控是保障 Web 应用质量与用户体验的关键基础设施。一个完善的前端监控体系能够帮助开发团队实时发现线上问题、量化性能表现、洞察用户行为,从而驱动产品体验的持续优化。本文将从错误监控、性能监控、埋点方案、Sentry 搭建、日志上报、监控看板设计以及监控方案选型等多个维度,全面介绍前端监控体系的建设方法与最佳实践。
错误监控
错误监控是前端监控体系中最基础也最核心的一环。任何线上环境中的 JavaScript 异常都可能导致功能不可用或页面白屏,及时捕获并定位错误来源至关重要。
JS 运行时错误
JavaScript 运行时错误通常通过 window.onerror 事件进行全局捕获。onerror 能够捕获大部分未处理的 JS 异常,包括语法错误、类型错误、引用错误等。其事件签名包含错误消息、脚本 URL、行号、列号和 Error 对象。需要注意,onerror 无法捕获资源加载失败(如图片、样式表)和 Promise 中抛出的异常。
Promise 未捕获
异步操作中未捕获的 Promise 拒绝(unhandled rejection)是前端生产环境中常见的错误来源。需要通过 window.onunhandledrejection 事件进行监听。该事件提供了 Promise 对象和拒绝原因,开发人员可以据此记录异步操作的失败详情。对于微前端或 iframe 场景,每个容器需要独立注册事件监听。
资源加载失败
图片、脚本、样式表、字体等静态资源加载失败时,浏览器不会触发 window.onerror,但可以通过监控 PerformanceObserver 中的 Resource Timing API 来检测失败条目,或在每个 <img>、<script>、<link> 标签上绑定 onerror 回调。更推荐的方式是利用 window.addEventListener('error', handler, true) 在捕获阶段拦截资源加载错误。
框架错误边界
现代前端框架(如 Vue、React)都有各自的错误处理机制,需要在框架层面进行集成,而不是仅依赖全局监听。
Vue errorHandler
在 Vue 3 中,可以通过 app.config.errorHandler 配置全局错误处理器。该处理器能够捕获组件渲染、计算属性、侦听器和生命周期钩子中抛出的所有未处理异常。此外,app.config.warnHandler 可以用于捕获开发环境的警告信息。
// Vue 3 错误处理器
app.config.errorHandler = (err, instance, info) => {
// err: Error 对象
// instance: 发生错误的组件实例
// info: 错误来源信息(如 'render function'、'setup function')
reportError({
type: 'vue.error',
message: err.message,
stack: err.stack,
component: instance?.type?.name,
info
})
}React error boundary
React 提供了错误边界(Error Boundary)机制,通过实现 componentDidCatch 生命周期方法或 static getDerivedStateFromError 来捕获子树中的渲染异常。错误边界只能捕获渲染阶段的错误,无法捕获事件处理器、异步代码和服务端渲染中的异常。
class ErrorBoundary extends React.Component {
componentDidCatch(error, info) {
reportError({
type: 'react.error',
message: error.message,
stack: error.stack,
componentStack: info.componentStack
})
}
}跨域脚本错误
当加载不同域名的脚本文件时,浏览器出于安全考虑会将错误信息中的具体内容遮蔽,只返回 Script error.。要解决这个问题,需要在跨域脚本的 <script> 标签上添加 crossorigin="anonymous" 属性,同时确保服务端返回 Access-Control-Allow-Origin 响应头。
性能监控
性能监控是衡量用户体验质量的核心手段。Google 提出的 Core Web Vitals 已成为业界标准,同时还需要结合 Navigation Timing、Resource Timing 等 API 进行全面监控。
Core Web Vitals
Core Web Vitals 是 Google 定义的一组用于量化用户体验的核心指标,包括 LCP、FID 和 CLS。这三个指标分别对应加载体验、交互体验和视觉稳定性,是搜索引擎排名算法的考量因素之一。
LCP(Largest Contentful Paint)
LCP 用于衡量页面主要内容加载完成的时间,统计视口中最大可见内容元素(图片、视频、大文本块)的渲染时间。良好的 LCP 标准应低于 2.5 秒。通过 PerformanceObserver 可以监听 largest-contentful-paint 类型的性能条目。
FID(First Input Delay)
FID 衡量用户首次与页面交互(点击按钮、输入文字等)到浏览器实际开始处理事件响应的时间间隔。FID 反映了页面的交互响应能力,良好的标准应低于 100 毫秒。FID 需要通过 PerformanceObserver 监听 first-input 条目。
CLS(Cumulative Layout Shift)
CLS 衡量页面生命周期内所有意外布局偏移的累计分数。布局偏移可能由图片未指定尺寸、动态注入内容、延迟加载字体等引起。良好的 CLS 标准应低于 0.1。通过 PerformanceObserver 监听 layout-shift 条目可以获取每次偏移的分数。
其他关键指标
FP 与 FCP
- FP(First Paint):浏览器首次将任何像素渲染到屏幕的时间点。
- FCP(First Contentful Paint):浏览器首次渲染任何文本、图片或 SVG 内容的时间点。
两者均可通过 PerformanceObserver 监听 paint 条目获取。
TTFB(Time to First Byte)
TTFB 测量从发起 HTTP 请求到浏览器接收到第一个字节数据的时间间隔。它反映了网络延迟和服务端处理速度。TTFB 可以通过 Navigation Timing API 中的 responseStart - requestStart 计算得出。
Navigation Timing
Navigation Timing API 提供了页面导航生命周期中每个阶段的精确时间戳,包括 DNS 查询、TCP 连接、TLS 握手、请求发送、响应接收和 DOM 解析等阶段。通过 performance.getEntriesByType('navigation') 可以获取完整的导航时间线。
const [navEntry] = performance.getEntriesByType('navigation')
const timing = {
dns: navEntry.domainLookupEnd - navEntry.domainLookupStart,
tcp: navEntry.connectEnd - navEntry.connectStart,
tls: navEntry.secureConnectionStart ? navEntry.connectEnd - navEntry.secureConnectionStart : 0,
ttfb: navEntry.responseStart - navEntry.requestStart,
domParse: navEntry.domComplete - navEntry.domInteractive
}Resource Timing
Resource Timing API 提供了页面中每个资源(脚本、样式、图片、字体等)的加载时序详情。通过 performance.getEntriesByType('resource') 可以获取所有资源的加载时间线,用于分析单个资源的加载瓶颈。
Long Tasks
Long Tasks 是指执行时间超过 50 毫秒的任务,它们会阻塞主线程,导致页面响应迟钝。通过 PerformanceObserver 监听 longtask 条目,可以识别并进行耗时任务拆分优化。
First Input Delay 的精确测量
FID 的精确测量依赖于 PerformanceObserver API,关键代码如下:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const fid = entry.processingStart - entry.startTime
reportMetric('FID', fid)
}
})
observer.observe({ type: 'first-input', buffered: true })埋点方案
埋点是用户行为数据采集的主要手段,不同的业务场景和精度要求对应不同的埋点方案。
埋点方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手动埋点 | 灵活可控、数据精确 | 开发成本高、容易遗漏 | 核心转化路径、关键交互 |
| 声明式埋点 | 与业务解耦、配置驱动 | 需要额外维护配置表 | 中大型项目、有配置平台 |
| 无埋点(全量采集) | 接入快、无遗漏 | 数据量大、分析成本高 | 快速验证、产品初期 |
| 可视化埋点 | 运营可自助操作 | 技术实现复杂 | 运营活动、动态埋点需求 |
事件模型
一个标准的事件模型包含以下核心字段:
interface TrackEvent {
event_name: string; // 事件名称,如 'button_click'
event_type: string; // 事件类型,如 'click'、'pageview'、'custom'
event_id: string; // 事件唯一标识
timestamp: number; // 事件发生时间戳
properties: Record<string, unknown>; // 事件属性
user: {
user_id?: string;
device_id: string;
session_id: string;
};
page: {
url: string;
referrer: string;
title: string;
};
device: {
ua: string;
screen: string;
viewport: string;
};
}用户行为追踪
用户行为追踪需要建立统一的用户标识体系。用户标识包括登录态标识(user_id)和匿名设备标识(device_id),通过 session 机制串联用户在单个会话中的一系列行为。
常见的用户行为事件包括:
- 页面浏览事件(page_view):记录用户访问了哪些页面
- 点击事件(click):记录用户点击了哪些元素
- 曝光事件(expose):记录元素的曝光情况
- 停留事件(stay):记录用户在页面的停留时长
- 自定义事件(custom):业务特定行为
页面 PV 与 UV
PV(Page View)统计页面被访问的次数,同一用户多次访问同一页面重复计数。UV(Unique Visitor)统计独立访客数,按设备 ID 或用户 ID 去重。PV/UV 统计通常需要配合时间维度(日、周、月)进行聚合分析。
function trackPageView() {
const event = {
event_name: 'page_view',
event_type: 'pageview',
properties: {
title: document.title,
url: window.location.href,
referrer: document.referrer
}
}
reportEvent(event)
}Sentry 搭建
Sentry 是目前业界使用最广泛的开源错误监控平台,支持前端和后端语言的错误追踪与性能监控。
Sentry SDK 配置
前端接入 Sentry 需要安装 @sentry/browser 或 @sentry/react 包。初始化时需配置 DSN(Data Source Name)和采样率等参数。
import * as Sentry from '@sentry/browser'
Sentry.init({
dsn: 'https://example@sentry.io/123456',
environment: 'production',
release: 'my-app@1.0.0',
sampleRate: 0.8, // 错误采样率
tracesSampleRate: 0.2, // 性能追踪采样率
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration()
]
})Source Map 上传
为了在 Sentry 面板中看到可读的源代码堆栈而不是混淆后的代码,需要上传 Source Map 文件。上传方式包括 Sentry CLI、Webpack 插件和直接 API 上传。
# 使用 Sentry CLI 上传 Source Map
sentry-cli releases files my-app@1.0.0 upload-sourcemaps ./dist \
--url-prefix '~/static/js' \
--rewrite安全提示:Source Map 文件应仅上传到 Sentry,不应部署到生产 CDN 上,避免源码泄露。在上传后务必从构建产物中删除。
Release 管理
Release 管理用于将错误与特定的发布版本关联,便于追溯问题引入的时间点。每次发布时应使用唯一的版本标识,并在 Sentry SDK 中通过 release 选项指定。发布流程中加入 Sentry Release 创建步骤,可以实现版本粒度的错误回归检测。
告警规则
Sentry 支持基于错误数量、用户影响范围、首次出现时间等维度设置告警规则。推荐配置的告警策略包括:
- 新增错误告警:首次出现的错误类型立即通知
- 错误增长告警:错误数量超过基线的一定比例时触发
- 用户影响告警:受影响用户数达到阈值时通知
- 性能告警:LCP 或 FCP 指标降级时触发
错误分组
Sentry 通过错误指纹(Fingerprint)机制对相似错误进行自动分组。默认情况下,Sentry 根据错误消息、堆栈帧和异常类型进行分组。可以通过自定义 beforeSend 回调修改指纹,将相似但堆栈略有不同的错误合并到同一个 Issue 中。
性能追踪
Sentry 的性能追踪功能通过 tracesSampleRate 配置采样率的分布式追踪。browserTracingIntegration 会自动创建页面导航的事务(Transaction),并将 LCP、FCP、TTFB 等 Web Vitals 指标作为 Span 记录。自定义操作可以通过 Sentry.startSpan 手动创建追踪 span。
Sentry.startSpan({
op: 'user.action',
name: 'Checkout Process'
}, async (span) => {
const result = await performCheckout()
span.setAttribute('cart.total', result.total)
return result
})日志上报
日志上报是监控数据从用户端传输到服务端的核心环节,需要考虑数据可靠性、传输效率和隐私合规等要素。
上报策略
前端日志上报主要有三种策略:
- 即时上报:事件发生后立即通过
fetch或XMLHttpRequest发送。适用于错误监控等对实时性要求高的场景。 - 批量上报:将多条日志暂存在内存中,达到一定数量(如 10 条)或定时(如 5 秒)后合并发送。适用于频繁触发的性能指标和行为事件。
- 空闲上报:利用
requestIdleCallback在浏览器空闲时段上报,避免影响主线程的渲染和交互任务。
采样与去重
高流量场景下需要对日志进行采样以控制存储成本。采样策略包括固定比例采样、自适应采样(根据错误频率动态调整采样率)和分层采样(对不同重要等级的事件采用不同采样率)。
去重机制用于防止相同错误被重复上报。可以通过在客户端缓存已上报的错误指纹(由错误类型、消息、堆栈前三行组合生成 hash),在短时间内不再重复发送同一错误。
批量上报与重试
批量上报的实现通常结合队列和定时器。上报失败时需要有指数退避(Exponential Backoff)重试机制,避免峰值流量冲击服务端。
class ReportQueue {
constructor() {
this.queue = []
this.maxSize = 10
this.retryLimit = 3
this.timer = null
}
push(event) {
this.queue.push(event)
if (this.queue.length >= this.maxSize) {
this.flush()
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), 5000)
}
}
async flush() {
if (this.queue.length === 0) return
const batch = this.queue.splice(0, this.maxSize)
try {
await this.send(batch)
} catch (err) {
batch.forEach((event) => {
event.retryCount = (event.retryCount || 0) + 1
if (event.retryCount < this.retryLimit) {
this.queue.unshift(event)
}
})
} finally {
this.timer = null
}
}
}上报格式
推荐使用 Protocol Buffers 或 JSON 格式进行数据序列化。JSON 格式的示例如下:
{
"app": "my-app",
"version": "1.0.0",
"env": "production",
"events": [
{
"type": "error",
"level": "error",
"timestamp": 1689123456789,
"message": "Cannot read property 'name' of undefined",
"stack": "TypeError: ...",
"fingerprint": "abc123def456",
"user_id": "u_12345",
"session_id": "s_67890",
"page_url": "https://example.com/checkout",
"extra": {}
}
]
}协议设计
上报协议设计需考虑以下要点:
- 压缩传输:使用 Gzip 或 Brotli 压缩请求体,减少带宽消耗
- HTTP 方法:使用 POST 方法,请求体包含批量事件数据
- 鉴权机制:使用 AppKey + 时间戳签名,防止伪造数据
- 响应确认:服务端返回 processed ID 列表,客户端只清除已确认的队列条目
监控看板设计
监控看板是数据价值的最终呈现形式,一个好的看板能够帮助团队快速定位问题、发现趋势。
错误看板
错误看板应包含以下模块:
- 错误概览:展示当前错误总数、新增错误数、受影响用户数等关键数字
- 错误类型分布:饼图或环形图展示 JS Error、Promise Error、Resource Error、Vue Error、React Error 等类型的占比
- 错误趋势图:按时间维度展示错误数量的折线图,支持按小时/天/周聚合
- 最近错误列表:按时间倒序展示最新错误,包含错误消息、影响用户数、首次/最近发生时间、处理状态等字段
- 错误详情页:点击单条错误后展示完整堆栈、发生环境、用户行为日志、关联 Session 回放
性能看板
性能看板应包含以下模块:
- 核心指标卡片:LCP、FID、CLS、FCP、TTFB 的当前值和目标阈值对比
- 性能趋势图:各指标在时间轴上的变化趋势,支持 P50、P75、P90、P99 分位线
- 慢页面 Top N:性能最差的页面排行榜,标记优化优先级
- 资源加载瀑布图:单个页面的资源加载时序分析
用户行为看板
用户行为看板应包含以下模块:
- PV/UV 统计:每日/每周/每月的 PV 和 UV 统计折线图
- 页面访问排行:页面 Top N 排行,展示 PV、UV、平均停留时长、跳出率
- 事件分析:关键事件的触发次数和用户渗透率
- 用户路径分析:用户在页面的跳转流转图
告警
告警系统是监控体系的闭环环节,支持多渠道通知(邮件、企业微信、钉钉、飞书、短信)。告警规则应支持多维度条件组合,如"错误率 > 5% 且持续时间 > 10 分钟"。为了避免告警疲劳,需要设置静默期和升级策略。
日报与周报
自动化日报/周报通过定时任务聚合监控数据,生成包含以下内容的报告:
- 整体趋势:核心指标在本周期内的变化趋势
- Top 问题:本期新增错误排行榜和处理建议
- 性能变化:核心 Web Vitals 的变化对比
- 服务可用性:页面可用率、API 成功率
RUM 与 Synthetic Monitoring 对比
Real User Monitoring(真实用户监控)
RUM 采集真实用户在生产环境中访问页面时的实际体验数据。其优势在于获得最真实的用户体验数据,反映的是用户在各种网络条件、设备和浏览器下的实际表现。缺点在于数据受样本分布影响较大,难以复现特定场景的问题。
Synthetic Monitoring(合成监控)
合成监控通过模拟脚本在受控环境中周期性访问页面,获取标准化的性能数据。其优势在于可重复、可对比,适用于预发布验证和生产环境的持续巡检。缺点是模拟数据不一定能反映真实用户的多变网络和设备环境。
对比总结
| 维度 | RUM | Synthetic Monitoring |
|---|---|---|
| 数据来源 | 真实用户访问 | 模拟脚本访问 |
| 覆盖范围 | 覆盖所有真实用户 | 有限的测试点 |
| 问题发现 | 被动发现已发生的性能问题 | 主动发现潜在问题 |
| 场景复现 | 难以复现特定用户场景 | 可精确复现测试场景 |
| 数据粒度 | 细粒度用户分群数据 | 标准化可对比数据 |
| 告警时效 | 滞后于用户体验 | 可第一时间发现异常 |
| 成本 | 存储和分析成本较高 | 基础设施和脚本维护成本 |
两种方案并非互相替代,而是互补关系。最佳实践是结合使用 RUM 和合成监控:用合成监控做主动巡检和预发布验证,用 RUM 做持续的用户体验度量和问题发现。