JavaScript系列(92)--前端监控体系
前端监控体系 📊
前端监控是现代Web应用不可或缺的组成部分,它帮助我们了解应用的运行状况、用户行为和潜在问题。本文将详细介绍如何构建一个完整的前端监控体系。
监控体系概述 🌟
💡 小知识:前端监控体系通常包括性能监控、错误监控、用户行为分析和业务监控四大模块,通过这些数据可以全面了解应用的健康状况和用户体验。
为什么需要前端监控
在复杂的前端应用中,监控系统能够帮助我们:
-
及时发现问题
- 捕获JS运行时错误
- 监控API请求异常
- 检测性能瓶颈
- 发现用户体验问题
-
优化用户体验
- 分析页面加载性能
- 监控交互响应时间
- 追踪用户行为路径
- 识别体验痛点
-
辅助业务决策
- 收集用户行为数据
- 分析功能使用情况
- 评估新功能效果
- 指导产品优化
-
提升开发效率
- 快速定位问题
- 复现错误场景
- 评估代码质量
- 指导性能优化
性能监控实现 ⚡
核心性能指标
// performance-metrics.ts
import { WebVitals } from './types';
export const collectWebVitals = (callback: (metrics: WebVitals) => void): void => {
// First Contentful Paint (FCP)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
callback({
name: 'FCP',
value: entry.startTime,
rating: getRating('FCP', entry.startTime)
});
});
}).observe({ entryTypes: ['paint'] });
// Largest Contentful Paint (LCP)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
callback({
name: 'LCP',
value: entry.startTime,
rating: getRating('LCP', entry.startTime)
});
});
}).observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay (FID)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
callback({
name: 'FID',
value: entry.processingStart - entry.startTime,
rating: getRating('FID', entry.duration)
});
});
}).observe({ entryTypes: ['first-input'] });
// Cumulative Layout Shift (CLS)
let clsValue = 0;
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
callback({
name: 'CLS',
value: clsValue,
rating: getRating('CLS', clsValue)
});
}
});
}).observe({ entryTypes: ['layout-shift'] });
};
// 性能指标评级
const getRating = (metric: string, value: number): 'good' | 'needs-improvement' | 'poor' => {
const thresholds = {
FCP: [1800, 3000],
LCP: [2500, 4000],
FID: [100, 300],
CLS: [0.1, 0.25]
};
const [good, poor] = thresholds[metric];
if (value <= good) return 'good';
if (value <= poor) return 'needs-improvement';
return 'poor';
};
资源加载监控
// resource-monitoring.ts
interface ResourceTiming {
name: string;
initiatorType: string;
duration: number;
transferSize: number;
startTime: number;
}
export const monitorResourceLoading = (): void => {
// 监控资源加载性能
new PerformanceObserver((list) => {
const resources = list.getEntries().map(entry => ({
name: entry.name,
initiatorType: entry.initiatorType,
duration: entry.duration,
transferSize: entry.transferSize,
startTime: entry.startTime
}));
// 分析资源加载情况
analyzeResources(resources);
}).observe({ entryTypes: ['resource'] });
};
const analyzeResources = (resources: ResourceTiming[]): void => {
// 按资源类型分组
const groupedResources = resources.reduce((acc, resource) => {
const type = resource.initiatorType;
if (!acc[type]) acc[type] = [];
acc[type].push(resource);
return acc;
}, {});
// 计算每种类型资源的统计信息
Object.entries(groupedResources).forEach(([type, items]) => {
const totalSize = items.reduce((sum, item) => sum + item.transferSize, 0);
const avgDuration = items.reduce((sum, item) => sum + item.duration, 0) / items.length;
console.log(`Resource Type: ${type}`);
console.log(`Total Size: ${(totalSize / 1024).toFixed(2)}KB`);
console.log(`Average Loading Time: ${avgDuration.toFixed(2)}ms`);
});
};
错误监控实现 🐛
全局错误捕获
// error-monitoring.ts
interface ErrorInfo {
type: string;
message: string;
stack?: string;
timestamp: number;
url: string;
userAgent: string;
}
export class ErrorMonitor {
private static instance: ErrorMonitor;
private errorQueue: ErrorInfo[] = [];
private readonly maxQueueSize = 100;
private readonly reportThreshold = 10;
private constructor() {
this.setupErrorListeners();
}
public static getInstance(): ErrorMonitor {
if (!ErrorMonitor.instance) {
ErrorMonitor.instance = new ErrorMonitor();
}
return ErrorMonitor.instance;
}
private setupErrorListeners(): void {
// 捕获JS运行时错误
window.addEventListener('error', (event) => {
this.captureError({
type: 'runtime',
message: event.message,
stack: event.error?.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
}, true);
// 捕获Promise未处理的rejection
window.addEventListener('unhandledrejection', (event) => {
this.captureError({
type: 'promise',
message: event.reason?.message || 'Promise Rejection',
stack: event.reason?.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
});
// 捕获资源加载错误
window.addEventListener('error', (event) => {
if (event.target && (event.target as HTMLElement).tagName) {
const target = event.target as HTMLElement;
this.captureError({
type: 'resource',
message: `Resource load failed: ${target.tagName.toLowerCase()}`,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
}
}, true);
}
private captureError(error: ErrorInfo): void {
this.errorQueue.push(error);
// 队列超过阈值时上报
if (this.errorQueue.length >= this.reportThreshold) {
this.reportErrors();
}
// 队列超过最大大小时清理
if (this.errorQueue.length > this.maxQueueSize) {
this.errorQueue = this.errorQueue.slice(-this.maxQueueSize);
}
}
private async reportErrors(): Promise<void> {
if (this.errorQueue.length === 0) return;
try {
const errors = [...this.errorQueue];
this.errorQueue = [];
await fetch('/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(errors)
});
} catch (error) {
console.error('Error reporting failed:', error);
// 报告失败时,将错误重新加入队列
this.errorQueue = [...this.errorQueue, ...errors];
}
}
}
React错误边界
// ErrorBoundary.tsx
import React, { Component, ErrorInfo } from 'react';
interface Props {
fallback: React.ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
children: React.ReactNode;
}
interface State {
hasError: boolean;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
// 上报错误到监控系统
ErrorMonitor.getInstance().captureError({
type: 'react',
message: error.message,
stack: error.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
}
render(): React.ReactNode {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
用户行为监控 👥
行为追踪实现
// behavior-tracking.ts
interface UserAction {
type: string;
target: string;
timestamp: number;
path: string;
metadata?: Record<string, any>;
}
export class BehaviorTracker {
private static instance: BehaviorTracker;
private actions: UserAction[] = [];
private readonly maxActions = 100;
private constructor() {
this.setupEventListeners();
}
public static getInstance(): BehaviorTracker {
if (!BehaviorTracker.instance) {
BehaviorTracker.instance = new BehaviorTracker();
}
return BehaviorTracker.instance;
}
private setupEventListeners(): void {
// 点击事件追踪
document.addEventListener('click', (event) => {
const target = event.target as HTMLElement;
this.trackAction({
type: 'click',
target: this.getElementPath(target),
timestamp: Date.now(),
path: window.location.pathname,
metadata: {
text: target.textContent?.trim(),
className: target.className
}
});
});
// 页面访问追踪
window.addEventListener('popstate', () => {
this.trackAction({
type: 'navigation',
target: window.location.pathname,
timestamp: Date.now(),
path: window.location.pathname
});
});
// 表单提交追踪
document.addEventListener('submit', (event) => {
const form = event.target as HTMLFormElement;
this.trackAction({
type: 'form_submit',
target: this.getElementPath(form),
timestamp: Date.now(),
path: window.location.pathname,
metadata: {
formId: form.id,
formAction: form.action
}
});
});
}
private getElementPath(element: HTMLElement): string {
const path: string[] = [];
let current = element;
while (current && current !== document.body) {
let selector = current.tagName.toLowerCase();
if (current.id) {
selector += `#${current.id}`;
} else if (current.className) {
selector += `.${current.className.split(' ').join('.')}`;
}
path.unshift(selector);
current = current.parentElement!;
}
return path.join(' > ');
}
private trackAction(action: UserAction): void {
this.actions.push(action);
// 超过最大数量时清理旧数据
if (this.actions.length > this.maxActions) {
this.actions = this.actions.slice(-this.maxActions);
}
// 异步上报数据
this.reportActions();
}
private async reportActions(): Promise<void> {
if (this.actions.length === 0) return;
try {
const actions = [...this.actions];
this.actions = [];
await fetch('/api/behavior', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(actions)
});
} catch (error) {
console.error('Behavior reporting failed:', error);
// 报告失败时,将行为数据重新加入队列
this.actions = [...this.actions, ...actions];
}
}
}
业务监控实现 📈
自定义事件追踪
// business-monitoring.ts
interface BusinessEvent {
category: string;
action: string;
label?: string;
value?: number;
timestamp: number;
metadata?: Record<string, any>;
}
export class BusinessMonitor {
private static instance: BusinessMonitor;
private events: BusinessEvent[] = [];
private readonly batchSize = 10;
private readonly reportInterval = 5000; // 5秒
private constructor() {
this.setupAutoReporting();
}
public static getInstance(): BusinessMonitor {
if (!BusinessMonitor.instance) {
BusinessMonitor.instance = new BusinessMonitor();
}
return BusinessMonitor.instance;
}
public trackEvent(event: Omit<BusinessEvent, 'timestamp'>): void {
this.events.push({
...event,
timestamp: Date.now()
});
// 达到批量上报阈值时立即上报
if (this.events.length >= this.batchSize) {
this.reportEvents();
}
}
private setupAutoReporting(): void {
// 定期上报数据
setInterval(() => {
this.reportEvents();
}, this.reportInterval);
}
private async reportEvents(): Promise<void> {
if (this.events.length === 0) return;
try {
const events = [...this.events];
this.events = [];
await fetch('/api/business-events', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(events)
});
} catch (error) {
console.error('Business event reporting failed:', error);
// 报告失败时,将事件重新加入队列
this.events = [...this.events, ...events];
}
}
}
// 使用示例
const monitor = BusinessMonitor.getInstance();
// 追踪用户注册
monitor.trackEvent({
category: 'user',
action: 'register',
label: 'email',
metadata: {
source: 'homepage',
campaign: 'spring_promotion'
}
});
// 追踪订单完成
monitor.trackEvent({
category: 'order',
action: 'complete',
value: 99.99,
metadata: {
orderId: 'ORDER123',
products: ['SKU1', 'SKU2']
}
});
数据分析与可视化 📊
数据聚合处理
// analytics.ts
interface AnalyticsData {
metrics: {
performance: {
fcp: number[];
lcp: number[];
fid: number[];
cls: number[];
};
errors: {
count: number;
types: Record<string, number>;
};
behavior: {
pageViews: number;
clicks: number;
formSubmits: number;
};
business: {
conversions: number;
revenue: number;
};
};
period: string;
}
export class AnalyticsProcessor {
public static aggregateData(rawData: any): AnalyticsData {
return {
metrics: {
performance: this.processPerformanceData(rawData.performance),
errors: this.processErrorData(rawData.errors),
behavior: this.processBehaviorData(rawData.behavior),
business: this.processBusinessData(rawData.business)
},
period: rawData.period
};
}
private static processPerformanceData(data: any) {
// 处理性能数据
return {
fcp: this.calculatePercentiles(data.fcp),
lcp: this.calculatePercentiles(data.lcp),
fid: this.calculatePercentiles(data.fid),
cls: this.calculatePercentiles(data.cls)
};
}
private static processErrorData(data: any) {
// 处理错误数据
return {
count: data.length,
types: data.reduce((acc, error) => {
acc[error.type] = (acc[error.type] || 0) + 1;
return acc;
}, {})
};
}
private static processBehaviorData(data: any) {
// 处理用户行为数据
return {
pageViews: data.filter(action => action.type === 'navigation').length,
clicks: data.filter(action => action.type === 'click').length,
formSubmits: data.filter(action => action.type === 'form_submit').length
};
}
private static processBusinessData(data: any) {
// 处理业务数据
return {
conversions: data.filter(event => event.category === 'conversion').length,
revenue: data
.filter(event => event.category === 'order' && event.action === 'complete')
.reduce((sum, event) => sum + (event.value || 0), 0)
};
}
private static calculatePercentiles(values: number[]): number[] {
const sorted = [...values].sort((a, b) => a - b);
return [
sorted[Math.floor(sorted.length * 0.5)], // p50
sorted[Math.floor(sorted.length * 0.75)], // p75
sorted[Math.floor(sorted.length * 0.9)], // p90
sorted[Math.floor(sorted.length * 0.95)] // p95
];
}
}
数据可视化组件
// visualization.tsx
import React from 'react';
import { Line, Bar, Pie } from 'react-chartjs-2';
import { AnalyticsData } from './types';
interface DashboardProps {
data: AnalyticsData;
}
export const AnalyticsDashboard: React.FC<DashboardProps> = ({ data }) => {
const performanceChartData = {
labels: ['P50', 'P75', 'P90', 'P95'],
datasets: [
{
label: 'FCP',
data: data.metrics.performance.fcp,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
},
{
label: 'LCP',
data: data.metrics.performance.lcp,
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
}
]
};
const errorChartData = {
labels: Object.keys(data.metrics.errors.types),
datasets: [{
data: Object.values(data.metrics.errors.types),
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 206, 86)',
'rgb(75, 192, 192)'
]
}]
};
const behaviorChartData = {
labels: ['Page Views', 'Clicks', 'Form Submits'],
datasets: [{
label: 'User Behavior',
data: [
data.metrics.behavior.pageViews,
data.metrics.behavior.clicks,
data.metrics.behavior.formSubmits
],
backgroundColor: 'rgb(54, 162, 235)'
}]
};
return (
<div className="analytics-dashboard">
<div className="chart-container">
<h3>Performance Metrics</h3>
<Line data={performanceChartData} />
</div>
<div className="chart-container">
<h3>Error Distribution</h3>
<Pie data={errorChartData} />
</div>
<div className="chart-container">
<h3>User Behavior</h3>
<Bar data={behaviorChartData} />
</div>
<div className="metrics-summary">
<div className="metric-card">
<h4>Total Errors</h4>
<p>{data.metrics.errors.count}</p>
</div>
<div className="metric-card">
<h4>Conversions</h4>
<p>{data.metrics.business.conversions}</p>
</div>
<div className="metric-card">
<h4>Revenue</h4>
<p>${data.metrics.business.revenue.toFixed(2)}</p>
</div>
</div>
</div>
);
};
监控系统最佳实践 ⭐
数据采集建议
-
采样策略
- 根据流量确定采样率
- 关键用户全量采集
- 错误日志优先级采集
- 性能数据定期采集
-
数据精简
- 只采集必要信息
- 合理设置采集频率
- 避免采集敏感数据
- 遵守数据隐私规范
-
上报优化
- 批量上报减少请求
- 使用信标API上报
- 考虑网络状况
- 失败重试机制
监控告警配置
// alert-system.ts
interface AlertRule {
metric: string;
condition: 'gt' | 'lt' | 'eq';
threshold: number;
duration: number; // 持续时间(分钟)
severity: 'low' | 'medium' | 'high';
}
interface Alert {
rule: AlertRule;
value: number;
timestamp: number;
status: 'active' | 'resolved';
}
export class AlertSystem {
private rules: AlertRule[] = [];
private alerts: Alert[] = [];
public addRule(rule: AlertRule): void {
this.rules.push(rule);
}
public checkMetric(metric: string, value: number): void {
const matchingRules = this.rules.filter(rule => rule.metric === metric);
for (const rule of matchingRules) {
const isViolated = this.evaluateRule(rule, value);
if (isViolated) {
this.createAlert(rule, value);
}
}
}
private evaluateRule(rule: AlertRule, value: number): boolean {
switch (rule.condition) {
case 'gt':
return value > rule.threshold;
case 'lt':
return value < rule.threshold;
case 'eq':
return value === rule.threshold;
default:
return false;
}
}
private createAlert(rule: AlertRule, value: number): void {
const alert: Alert = {
rule,
value,
timestamp: Date.now(),
status: 'active'
};
this.alerts.push(alert);
this.notifyAlert(alert);
}
private async notifyAlert(alert: Alert): Promise<void> {
// 根据告警级别选择通知方式
switch (alert.rule.severity) {
case 'high':
await this.sendUrgentNotification(alert);
break;
case 'medium':
await this.sendEmailNotification(alert);
break;
case 'low':
await this.sendSystemNotification(alert);
break;
}
}
private async sendUrgentNotification(alert: Alert): Promise<void> {
// 发送紧急通知(如电话、短信)
}
private async sendEmailNotification(alert: Alert): Promise<void> {
// 发送邮件通知
}
private async sendSystemNotification(alert: Alert): Promise<void> {
// 发送系统内部通知
}
}
结语 📝
前端监控体系是保障应用质量和用户体验的重要工具。通过本文,我们学习了:
- 前端监控的重要性和基本概念
- 性能监控的实现方法
- 错误监控和异常处理
- 用户行为追踪技术
- 业务监控的实现
- 数据分析和可视化方案
💡 学习建议:
- 从基础监控开始,逐步扩展监控范围
- 注重数据安全和用户隐私保护
- 持续优化监控系统性能
- 根据实际需求调整监控策略
- 重视监控数据的分析和应用
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻