引言
在现代前端开发中,监控体系是保障用户体验和系统稳定性的关键基础设施。一个完善的前端监控体系能够帮助我们及时发现性能问题、定位错误根源、理解用户行为,从而持续优化产品体验。本文将深入探讨前端监控的三大核心维度:性能监控、错误监控和行为监控。
一、性能监控
1.1 核心性能指标
首屏加载时间 ( FCP - First Contentful Paint )
使用 Performance API 获取 FCP:
javascript
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log(`FCP: ${entry.startTime}ms`);
reportMetric('fcp', entry.startTime);
}
}
});
observer.observe({ entryTypes: ['paint'] });
最大内容绘制 ( LCP - Largest Contentful Paint )
ini
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log(`LCP: ${lastEntry.startTime}ms`);
reportMetric('lcp', lastEntry.startTime);
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
累积布局偏移 ( CLS - Cumulative Layout Shift )
ini
let clsValue = 0;
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
console.log(`CLS: ${clsValue}`);
reportMetric('cls', clsValue);
}
}
});
clsObserver.observe({ entryTypes: ['layout-shift'] });
1.2 自定义性能埋点
javascript
class PerformanceMonitor {
constructor() {
this.metrics = {};
}
recordPageLoad() {
const timing = performance.timing;
const metrics = {
dnsLookup: timing.domainLookupEnd - timing.domainLookupStart,
tcpConnect: timing.connectEnd - timing.connectStart,
domParse: timing.domComplete - timing.domLoading,
fullLoad: timing.loadEventEnd - timing.navigationStart
};
Object.entries(metrics).forEach(([key, value]) => {
this.reportMetric(`page_${key}`, value);
});
}
recordResourceLoad() {
performance.getEntriesByType('resource').forEach(resource => {
if (resource.duration > 1000) {
this.reportMetric('slow_resource', {
name: resource.name,
duration: resource.duration,
type: resource.initiatorType
});
}
});
}
reportMetric(name, value) {
fetch('/api/metrics', {
method: 'POST',
body: JSON.stringify({ name, value, timestamp: Date.now() }),
headers: { 'Content-Type': 'application/json' }
});
}
}
const perfMonitor = new PerformanceMonitor();
perfMonitor.recordPageLoad();
perfMonitor.recordResourceLoad();
二、错误监控
2.1 全局错误捕获
JavaScript 运行时错误
php
window.addEventListener('error', (event) => {
reportError({
type: 'runtime',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now()
});
}, true);
window.addEventListener('unhandledrejection', (event) => {
reportError({
type: 'promise',
message: event.reason?.message || 'Unhandled Promise Rejection',
stack: event.reason?.stack,
timestamp: Date.now()
});
});
Vue 错误捕获
php
import { createApp } from 'vue';
const app = createApp(App);
app.config.errorHandler = (err, instance, info) => {
reportError({
type: 'vue',
message: err.message,
stack: err.stack,
component: instance?.name || 'Anonymous',
lifecycleHook: info,
timestamp: Date.now()
});
};
React 错误边界
javascript
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
reportError({
type: 'react',
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
timestamp: Date.now()
});
}
render() {
if (this.state.hasError) {
return <div>页面出错了,请稍后刷新</div>;
}
return this.props.children;
}
}
2.2 资源加载错误
php
window.addEventListener('error', (event) => {
if (event.target instanceof HTMLImageElement) {
reportError({
type: 'resource',
resourceType: 'image',
url: event.target.src,
timestamp: Date.now()
});
}
}, true);
const originalFetch = window.fetch;
window.fetch = async function(...args) {
try {
const response = await originalFetch(...args);
if (!response.ok) {
reportError({
type: 'http',
url: args[0],
status: response.status,
method: args[1]?.method || 'GET'
});
}
return response;
} catch (error) {
reportError({
type: 'http',
url: args[0],
message: error.message,
method: args[1]?.method || 'GET'
});
throw error;
}
};
三、行为监控
3.1 用户交互追踪
javascript
class BehaviorTracker {
constructor() {
this.sessionId = this.generateSessionId();
this.pageViewTime = 0;
}
trackClick(element) {
const eventData = {
type: 'click',
element: element.tagName,
className: element.className,
text: element.textContent?.slice(0, 50),
x: element.getBoundingClientRect().left,
y: element.getBoundingClientRect().top,
pageUrl: window.location.href,
timestamp: Date.now()
};
this.reportBehavior(eventData);
}
trackPageView() {
const startTime = Date.now();
window.addEventListener('beforeunload', () => {
const duration = Date.now() - startTime;
this.reportBehavior({
type: 'pageview',
url: window.location.href,
duration,
sessionId: this.sessionId,
timestamp: Date.now()
});
});
}
trackScroll() {
let scrollTimeout;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
const scrollDepth = Math.round(
(window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
);
this.reportBehavior({
type: 'scroll',
scrollDepth,
timestamp: Date.now()
});
}, 500);
});
}
reportBehavior(data) {
navigator.sendBeacon('/api/behavior', JSON.stringify(data));
}
generateSessionId() {
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
const tracker = new BehaviorTracker();
tracker.trackPageView();
tracker.trackScroll();
document.addEventListener('click', (event) => {
tracker.trackClick(event.target);
});
3.2 性能与行为关联分析
javascript
class Analytics {
constructor() {
this.userActions = [];
}
recordAction(action) {
this.userActions.push({
...action,
sessionId: this.getSessionId(),
userId: this.getUserId()
});
}
getSessionId() {
return localStorage.getItem('session_id') ||
(localStorage.setItem('session_id', `sess_${Date.now()}`),
localStorage.getItem('session_id'));
}
getUserId() {
return localStorage.getItem('user_id') || 'anonymous';
}
analyzePerformanceAfterAction(actionType) {
const actions = this.userActions.filter(a => a.type === actionType);
const recentActions = actions.slice(-10);
return {
avgResponseTime: recentActions.reduce((sum, a) => sum + (a.responseTime || 0), 0) / recentActions.length,
errorRate: recentActions.filter(a => a.error).length / recentActions.length
};
}
}
四、监控数据上报与可视化
4.1 统一上报服务
kotlin
class MonitorService {
constructor(config) {
this.endpoint = config.endpoint;
this.appId = config.appId;
this.queue = [];
this.flushInterval = 5000;
this.init();
}
init() {
setInterval(() => this.flush(), this.flushInterval);
window.addEventListener('beforeunload', () => this.flush());
}
report(type, data) {
const payload = {
appId: this.appId,
type,
data,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href,
referrer: document.referrer
};
this.queue.push(payload);
if (type === 'error') {
this.flush();
}
}
flush() {
if (this.queue.length === 0) return;
const data = [...this.queue];
this.queue = [];
navigator.sendBeacon(`${this.endpoint}/batch`, JSON.stringify(data));
}
}
const monitor = new MonitorService({
endpoint: 'https://monitor.example.com',
appId: 'frontend-app-001'
});
4.2 告警规则
yaml
const alertRules = {
errorRate: { threshold: 0.05, window: 300 },
fcp: { threshold: 2500 },
lcp: { threshold: 4000 },
cls: { threshold: 0.25 },
apiErrorRate: { threshold: 0.1, window: 60 }
};
function checkAlerts(metric, value) {
const rule = alertRules[metric];
if (!rule) return;
if (value > rule.threshold) {
sendAlert({
metric,
value,
threshold: rule.threshold,
timestamp: Date.now()
});
}
}
总结
一个完善的前端监控体系应该包含:
- 性能监控:关注 FCP、LCP、CLS 等核心指标,持续优化加载体验
- 错误监控:全面捕获运行时错误、资源错误、HTTP 错误,快速定位问题
- 行为监控:追踪用户交互,理解用户行为模式
通过统一的数据上报和告警机制,我们可以:
- 及时发现并修复问题
- 持续优化性能表现
- 提升用户体验
- 降低运维成本
记住:监控不是为了发现问题,而是为了预防问题。建立完善的监控体系,让前端开发更加从容!