React项目监控与日志记录实战指南:从零搭建高可用系统,快速定位前端性能瓶颈与错误根源

引言:为什么前端监控至关重要
在现代Web开发中,React项目往往面临复杂的用户环境和多样的使用场景。用户可能使用不同版本的浏览器、不同的设备性能,甚至在网络状况不佳的情况下访问应用。当用户反馈”页面卡顿”或”突然白屏”时,如果没有完善的监控系统,开发者就像在黑暗中摸索,难以快速定位问题根源。
前端监控系统主要解决三大核心问题:
性能监控
:识别页面加载慢、交互卡顿等性能瓶颈
错误监控
:捕获JS异常、API请求失败、资源加载失败等错误
用户行为追踪
:理解用户如何使用应用,发现使用路径中的问题
一个完整的监控系统通常包含数据采集、数据传输、数据存储和数据可视化四个环节。本文将从零开始,教你如何在React项目中搭建一套完整的监控与日志记录系统。
一、监控系统架构设计
1.1 整体架构概述
在开始编码之前,我们需要明确监控系统的整体架构:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 客户端采集SDK │───▶│ 数据收集服务 │───▶│ 数据存储与分析 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ React应用集成 │ │ API网关/消息队列 │ │ 可视化展示平台 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
1.2 技术选型建议
前端采集SDK
:
Sentry
:功能强大的错误监控平台,支持Source Map定位
自研SDK
:轻量级,可定制性强,适合有特殊需求的团队
数据传输
:
Beacon API
:页面卸载时可靠发送数据
Fetch/XHR
:常规数据传输方式
WebSocket
:实时性要求高的场景
数据存储
:
Elasticsearch
:日志存储和搜索
InfluxDB
:时序数据存储(性能指标)
ClickHouse
:大规模数据分析
可视化
:
Grafana
:监控仪表盘
自研Dashboard
:定制化展示
1.3 监控数据分类
我们需要采集的数据主要包括:
数据类型
采集时机
关键指标
性能数据
页面加载、路由切换、组件渲染
FCP、LCP、TTI、FPS
错误数据
异常抛出、Promise拒绝、资源加载失败
错误堆栈、发生时间、用户环境
行为数据
用户点击、输入、页面跳转
用户ID、操作序列、停留时长
环境数据
页面初始化
浏览器版本、设备信息、网络状态
二、搭建基础监控SDK
2.1 创建监控SDK核心类
首先,我们创建一个轻量级的监控SDK,它将作为我们所有监控能力的基础。
// src/monitor/sdk.js
class MonitorSDK {
constructor(config) {
this.config = {
dsn: ”, // 数据上报地址
appVersion: ”, // 应用版本
userId: ”, // 用户标识
sampleRate: 1.0, // 采样率
maxQueueSize: 100, // 最大队列长度
…config
};

this.queue = []; // 数据队列
this.isReady = false;
this.init();
}

// 初始化方法
init() {
// 检查必要配置
if (!this.config.dsn) {
console.warn(‘MonitorSDK: dsn is required’);
return;
}

// 绑定错误监控
this.bindErrorMonitor();
// 绑定性能监控
this.bindPerformanceMonitor();
// 绑定用户行为监控
this.bindBehaviorMonitor();

this.isReady = true;
console.log(‘MonitorSDK initialized’);
}

// 核心上报方法
report(data, type = ‘custom’) {
// 采样率检查
if (Math.random() > this.config.sampleRate) {
return;
}

// 队列长度检查
if (this.queue.length >= this.config.maxQueueSize) {
this.queue.shift(); // 移除最早的数据
}

const payload = {
type,
timestamp: Date.now(),
userId: this.config.userId,
appVersion: this.config.appVersion,
userAgent: navigator.userAgent,
url: window.location.href,
…data
};

this.queue.push(payload);
this.flush(); // 尝试立即上报
}

// 立即上报队列中的数据
flush() {
if (this.queue.length === 0) return;

const data = […this.queue];
this.queue = [];

// 使用Beacon API优先,失败时降级到fetch
if (navigator.sendBeacon) {
const blob = new Blob([JSON.stringify(data)], { type: ‘application/json’ });
navigator.sendBeacon(this.config.dsn, blob);
} else {
fetch(this.config.dsn, {
method: ‘POST’,
body: JSON.stringify(data),
headers: { ‘Content-Type’: ‘application/json’ },
keepalive: true
}).catch(err => {
// 上报失败,将数据重新加入队列
this.queue.unshift(…data);
console.error(‘Monitor report failed:’, err);
});
}
}

// 手动上报接口
log(message, level = ‘info’, context = {}) {
this.report({
message,
level,
context
}, ‘log’);
}

// 错误上报接口
error(error, context = {}) {
const errorInfo = {
message: error.message,
stack: error.stack,
name: error.name,
…context
};
this.report(errorInfo, ‘error’);
}
}

// 单例模式导出
export default new MonitorSDK();
2.2 错误监控实现
错误监控需要捕获多种类型的错误:
// 在MonitorSDK类中添加bindErrorMonitor方法
bindErrorMonitor() {
// 1. 捕获全局JS错误
window.addEventListener(‘error’, (event) => {
// 过滤资源错误
if (event.target !== window) {
this.report({
type: ‘resource_error’,
resourceType: event.target.tagName,
resourceUrl: event.target.src || event.target.href,
message: event.message || ‘Resource load failed’
}, ‘error’);
return;
}

this.report({
type: ‘js_error’,
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack
}, ‘error’);
}, true);

// 2. 捕获未处理的Promise拒绝
window.addEventListener(‘unhandledrejection’, (event) => {
this.report({
type: ‘promise_rejection’,
reason: event.reason?.message || String(event.reason),
stack: event.reason?.stack
}, ‘error’);
});

// 3. 捕获React错误边界
// 这需要在React组件中配合使用,稍后会讲到
}
2.3 性能监控实现
性能监控需要收集页面加载和运行时的性能指标:
// 在MonitorSDK类中添加bindPerformanceMonitor方法
bindPerformanceMonitor() {
// 页面加载性能
window.addEventListener(‘load’, () => {
setTimeout(() => {
this.collectPerformanceMetrics();
}, 0);
});

// 路由变化性能(针对SPA)
this.monitorRouteChange();

// 运行时性能(FPS、内存等)
this.monitorRuntimePerformance();
}

collectPerformanceMetrics() {
if (!window.performance) return;

const timing = performance.getEntriesByType(‘navigation’)[0];
if (!timing) return;

const metrics = {
// 关键性能指标
dns: timing.domainLookupEnd – timing.domainLookupStart,
tcp: timing.connectEnd – timing.connectStart,
ttfb: timing.responseStart – timing.requestStart, // 首字节时间
download: timing.responseEnd – timing.responseStart,
domReady: timing.domContentLoadedEventEnd – timing.domContentLoadedEventStart,
load: timing.loadEventEnd – timing.loadEventStart,

// 用户感知指标
fcp: 0, // 首次内容绘制
lcp: 0, // 最大内容绘制
cls: 0, // 累积布局偏移
tti: 0, // 可交互时间
fid: 0 // 首次输入延迟
};

// 使用PerformanceObserver获取更精确的指标
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === ‘first-contentful-paint’) {
metrics.fcp = entry.startTime;
}
if (entry.name === ‘largest-contentful-paint’) {
metrics.lcp = entry.startTime;
}
if (entry.entryType === ‘layout-shift’) {
metrics.cls += entry.value;
}
if (entry.entryType === ‘first-input’) {
metrics.fid = entry.processingStart – entry.startTime;
}
}
});

try {
observer.observe({ entryTypes: [‘paint’, ‘layout-shift’, ‘first-input’] });
} catch (e) {
// 浏览器兼容性处理
}

// 延迟一点发送,确保所有指标都已收集
setTimeout(() => {
this.report(metrics, ‘performance’);
observer.disconnect();
}, 1000);
}

monitorRouteChange() {
// 监控SPA路由变化性能
if (window.__REACT_ROUTER__) {
// 如果使用React Router,可以监听路由变化
const originalPushState = history.pushState;
history.pushState = (…args) => {
const startTime = performance.now();
originalPushState.apply(history, args);

setTimeout(() => {
const duration = performance.now() – startTime;
this.report({
type: ‘route_change’,
from: window.location.href,
to: args[2] || ”,
duration
}, ‘performance’);
}, 0);
};
}
}

monitorRuntimePerformance() {
// 监控FPS
let lastTime = performance.now();
let frames = 0;

const measureFPS = () => {
frames++;
const currentTime = performance.now();
if (currentTime >= lastTime + 1000) {
const fps = Math.round((frames * 1000) / (currentTime – lastTime));

if (fps < 30) { this.report({ type: 'low_fps', fps, timestamp: currentTime }, 'performance'); } frames = 0; lastTime = currentTime; } requestAnimationFrame(measureFPS); }; requestAnimationFrame(measureFPS); // 监控内存使用(Chrome DevTools Protocol需要) if (performance.memory) { setInterval(() => {
const memory = performance.memory;
const usedMB = Math.round(memory.usedJSHeapSize / 1048576);
const totalMB = Math.round(memory.totalJSHeapSize / 1048576);

if (usedMB > 100) { // 超过100MB警告
this.report({
type: ‘memory_usage’,
usedMB,
totalMB,
limit: memory.jsHeapSizeLimit
}, ‘performance’);
}
}, 30000); // 每30秒检查一次
}
}
2.4 用户行为监控
// 在MonitorSDK类中添加bindBehaviorMonitor方法
bindBehaviorMonitor() {
// 监控点击事件
document.addEventListener(‘click’, (event) => {
const target = event.target;
const elementInfo = this.getElementInfo(target);

this.report({
type: ‘click’,
element: elementInfo,
timestamp: performance.now()
}, ‘behavior’);
}, true);

// 监控页面可见性变化
document.addEventListener(‘visibilitychange’, () => {
if (document.visibilityState === ‘hidden’) {
// 页面隐藏时立即上报
this.flush();
}
});

// 监控页面卸载
window.addEventListener(‘beforeunload’, () => {
this.flush();
});
}

getElementInfo(element) {
if (!element) return null;

const info = {
tag: element.tagName,
id: element.id,
className: element.className,
text: element.textContent?.slice(0, 50) // 限制长度
};

// 尝试获取React组件信息
if (element.__reactFiber$) {
const fiber = element.__reactFiber$;
info.reactComponent = fiber.type?.name || fiber.type?.displayName;
}

return info;
}
三、React项目集成
3.1 在React应用中初始化SDK
// src/monitor/init.js
import monitorSDK from ‘./sdk’;

// 初始化监控SDK
export function initMonitor() {
// 从环境变量或配置文件获取DSN
const dsn = process.env.REACT_APP_MONITOR_DSN || ‘https://your-monitor-api.com/collect’;

monitorSDK.init({
dsn,
appVersion: process.env.REACT_APP_VERSION || ‘1.0.0’,
userId: getUserId(), // 获取用户ID
sampleRate: 0.8, // 80%采样率
maxQueueSize: 50
});
}

// 获取用户ID(可以是匿名ID或登录用户ID)
function getUserId() {
// 从localStorage获取或生成
let userId = localStorage.getItem(‘monitor_user_id’);
if (!userId) {
userId = `anon_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
localStorage.setItem(‘monitor_user_id’, userId);
}
return userId;
}

// 在应用入口调用
// src/index.js
import { initMonitor } from ‘./monitor/init’;

initMonitor();
3.2 React错误边界集成
React 16+提供了Error Boundaries来捕获组件树中的错误,我们可以将其与监控SDK结合:
// src/components/ErrorBoundary.jsx
import React from ‘react’;
import monitorSDK from ‘../monitor/sdk’;

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error) {
// 更新state使下一次渲染能够显示降级UI
return { hasError: true, error };
}

componentDidCatch(error, errorInfo) {
// 记录错误到监控系统
monitorSDK.error(error, {
componentStack: errorInfo.componentStack,
componentName: this.props.componentName || ‘Unknown’,
props: JSON.stringify(this.props),
state: JSON.stringify(this.state)
});

// 可以在这里调用额外的错误处理逻辑
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}

render() {
if (this.state.hasError) {
// 可以返回自定义的降级UI
return (

抱歉,出现了一些问题

我们的工程师正在努力修复…

);
}

return this.props.children;
}
}

// 高阶组件形式
export function withErrorBoundary(Component, options = {}) {
return function WrappedComponent(props) {
return (



);
};
}

export default ErrorBoundary;
使用方式:
// 在App.js中使用
import ErrorBoundary from ‘./components/ErrorBoundary’;

function App() {
return (



);
}

// 或者包裹特定组件
import { withErrorBoundary } from ‘./components/ErrorBoundary’;

const UserProfile = ({ userId }) => {
// 可能出错的组件
return

{/* … */}

;
};

export default withErrorBoundary(UserProfile, {
onError: (error) => {
console.error(‘User profile error:’, error);
}
});
3.3 React性能监控集成
3.3.1 组件渲染性能监控
// src/monitor/reactPerformance.js
import monitorSDK from ‘./sdk’;

// 高阶组件:监控组件渲染性能
export function withPerformanceMonitor(Component, options = {}) {
const WrappedComponent = (props) => {
const startTime = performance.now();
const result = ;
const endTime = performance.now();

const renderTime = endTime – startTime;

// 如果渲染时间超过阈值,记录警告
if (renderTime > options.threshold || 50) {
monitorSDK.report({
type: ‘slow_component_render’,
componentName: Component.displayName || Component.name,
renderTime,
props: JSON.stringify(props)
}, ‘performance’);
}

return result;
};

WrappedComponent.displayName = `WithPerformanceMonitor(${
Component.displayName || Component.name
})`;

return WrappedComponent;
}

// React Hook: 监控自定义Hook性能
export function usePerformanceMonitor(hookName, threshold = 50) {
const startTimeRef = useRef(performance.now());

useEffect(() => {
const endTime = performance.now();
const duration = endTime – startTimeRef.current;

if (duration > threshold) {
monitorSDK.report({
type: ‘slow_hook_execution’,
hookName,
duration
}, ‘performance’);
}
});
}
3.3.2 长任务监控
// 监控主线程阻塞
export function monitorLongTasks() {
if (‘PerformanceObserver’ in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) { // 超过50ms的任务
monitorSDK.report({
type: ‘long_task’,
duration: entry.duration,
startTime: entry.startTime,
name: entry.name
}, ‘performance’);
}
}
});

try {
observer.observe({ entryTypes: [‘longtask’] });
} catch (e) {
console.warn(‘Long task monitoring not supported’);
}
}
}
3.4 API请求监控
// src/monitor/apiMonitor.js
import monitorSDK from ‘./sdk’;

// 拦截fetch请求
export function interceptFetch() {
const originalFetch = window.fetch;

window.fetch = async function(…args) {
const startTime = performance.now();
const url = typeof args[0] === ‘string’ ? args[0] : args[0].url;

try {
const response = await originalFetch.apply(this, args);
const endTime = performance.now();

// 记录成功请求
monitorSDK.report({
type: ‘api_success’,
url,
method: (args[1]?.method || ‘GET’).toUpperCase(),
status: response.status,
duration: endTime – startTime
}, ‘api’);

return response;
} catch (error) {
const endTime = performance.now();

// 记录失败请求
monitorSDK.error(error, {
type: ‘api_error’,
url,
method: (args[1]?.method || ‘GET’).toUpperCase(),
duration: endTime – startTime,
errorMessage: error.message
});

throw error;
}
};
}

// 拦截XMLHttpRequest
export function interceptXHR() {
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.open = function(method, url) {
this._monitor_url = url;
this._monitor_method = method;
return originalOpen.apply(this, arguments);
};

XMLHttpRequest.prototype.send = function() {
const startTime = performance.now();
const url = this._monitor_url;
const method = this._monitor_method;

this.addEventListener(‘load’, function() {
const endTime = performance.now();
monitorSDK.report({
type: ‘api_success’,
url,
method: method.toUpperCase(),
status: this.status,
duration: endTime – startTime
}, ‘api’);
});

this.addEventListener(‘error’, function() {
const endTime = performance.now();
monitorSDK.error(new Error(‘XHR failed’), {
type: ‘api_error’,
url,
method: method.toUpperCase(),
duration: endTime – startTime
});
});

return originalSend.apply(this, arguments);
};
}

// React Query/Axios拦截器
export function setupAxiosInterceptors(axiosInstance) {
// 请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
config.metadata = { startTime: performance.now() };
return config;
},
(error) => {
return Promise.reject(error);
}
);

// 响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
const duration = performance.now() – response.config.metadata.startTime;
monitorSDK.report({
type: ‘api_success’,
url: response.config.url,
method: response.config.method.toUpperCase(),
status: response.status,
duration
}, ‘api’);
return response;
},
(error) => {
const duration = performance.now() – error.config.metadata.startTime;
monitorSDK.error(error, {
type: ‘api_error’,
url: error.config.url,
method: error.config.method.toUpperCase(),
status: error.response?.status,
duration
});
return Promise.reject(error);
}
);
}
四、数据上报与存储
4.1 数据上报策略
// src/monitor/reporter.js
import monitorSDK from ‘./sdk’;

// 批量上报策略
class BatchReporter {
constructor() {
this.batchSize = 10; // 每批10条
this.interval = 5000; // 每5秒上报一次
this.timer = null;
this.init();
}

init() {
// 页面可见性变化时处理
document.addEventListener(‘visibilitychange’, () => {
if (document.visibilityState === ‘hidden’) {
this.flush();
}
});

// 页面卸载时处理
window.addEventListener(‘beforeunload’, () => {
this.flush();
});

// 定时上报
this.timer = setInterval(() => {
this.flush();
}, this.interval);
}

flush() {
const queue = monitorSDK.getQueue();
if (queue.length === 0) return;

// 分批上报
const batches = [];
for (let i = 0; i < queue.length; i += this.batchSize) { batches.push(queue.slice(i, i + this.batchSize)); } batches.forEach(batch => {
this.sendBatch(batch);
});
}

async sendBatch(batch) {
try {
const response = await fetch(monitorSDK.config.dsn, {
method: ‘POST’,
body: JSON.stringify(batch),
headers: { ‘Content-Type’: ‘application/json’ },
keepalive: true
});

if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}

// 上报成功,从队列中移除
monitorSDK.removeFromQueue(batch);
} catch (error) {
console.error(‘Batch report failed:’, error);
// 失败时保留数据,下次重试
}
}
}

export default BatchReporter;
4.2 后端接收服务示例
虽然本文重点在前端,但提供一个简单的Node.js接收服务示例:
// server/monitor-receiver.js
const express = require(‘express’);
const { InfluxDB, Point } = require(‘@influxdata/influxdb-client’);
const app = express();

// 配置InfluxDB(用于存储性能指标)
const influx = new InfluxDB({ url: ‘http://localhost:8086’, token: ‘your-token’ });
const writeApi = influx.getWriteApi(‘your-org’, ‘monitor-bucket’);

app.use(express.json({ limit: ’10mb’ }));

// 接收监控数据
app.post(‘/collect’, async (req, res) => {
const data = req.body;

if (!Array.isArray(data)) {
return res.status(400).json({ error: ‘Data must be an array’ });
}

try {
// 处理每条数据
for (const item of data) {
// 根据类型存储到不同系统
if (item.type === ‘performance’) {
// 存储到InfluxDB(时序数据)
const point = new Point(‘performance_metrics’)
.tag(‘app_version’, item.appVersion)
.tag(‘user_id’, item.userId)
.floatField(‘fcp’, item.fcp)
.floatField(‘lcp’, item.lcp)
.floatField(‘cls’, item.cls)
.timestamp(item.timestamp);

writeApi.writePoint(point);
} else if (item.type === ‘error’) {
// 存储到Elasticsearch(全文搜索)
await elasticsearch.index({
index: ‘monitor-errors’,
body: {
timestamp: new Date(item.timestamp),
…item
}
});
} else if (item.type === ‘log’) {
// 存储到ClickHouse(日志分析)
await clickhouse.insert({
table: ‘monitor_logs’,
values: [{
timestamp: new Date(item.timestamp),
message: item.message,
level: item.level,
user_id: item.userId
}]
});
}
}

// 批量写入InfluxDB
await writeApi.close();

res.json({ success: true, count: data.length });
} catch (error) {
console.error(‘Monitor data processing error:’, error);
res.status(500).json({ error: ‘Internal server error’ });
}
});

// 查询接口示例
app.get(‘/monitor/performance’, async (req, res) => {
const { start, end, appVersion } = req.query;

// 查询InfluxDB获取性能数据
const query = `
from(bucket: “monitor-bucket”)
|> range(start: ${start}, end: ${end})
|> filter(fn: (r) => r._measurement == “performance_metrics”)
|> filter(fn: (r) => r.app_version == “${appVersion}”)
|> aggregateWindow(every: 1m, fn: mean)
`;

const result = await influx.getQueryApi(‘your-org’).collectRows(query);
res.json(result);
});

app.listen(3001, () => {
console.log(‘Monitor receiver running on port 3001’);
});
五、可视化与告警
5.1 Grafana仪表盘配置
虽然Grafana配置通常在UI中完成,但这里提供一个JSON配置示例:
{
“dashboard”: {
“title”: “React应用监控”,
“panels”: [
{
“id”: 1,
“title”: “页面加载性能”,
“type”: “graph”,
“targets”: [
{
“datasource”: “InfluxDB”,
“query”: “SELECT mean(\”fcp\”) FROM \”performance_metrics\” WHERE $timeFilter GROUP BY time($__interval)”
}
]
},
{
“id”: 2,
“title”: “错误率趋势”,
“type”: “stat”,
“targets”: [
{
“datasource”: “Elasticsearch”,
“query”: “type:error AND timestamp:[now-1h TO now]”
}
],
“thresholds”: {
“mode”: “absolute”,
“steps”: [
{“color”: “green”, “value”: null},
{“color”: “red”, “value”: 10}
]
}
}
]
}
}
5.2 告警规则示例
# alert-rules.yml
groups:
– name: react_app_alerts
rules:
– alert: HighErrorRate
expr: rate(errors_total[5m]) > 0.1
for: 5m
labels:
severity: critical
annotations:
summary: “高错误率告警”
description: “应用错误率超过10%,当前值: {{ $value }}”

– alert: SlowPageLoad
expr: avg(lcp) > 2500
for: 10m
labels:
severity: warning
annotations:
summary: “页面加载缓慢”
description: “LCP指标超过2.5秒,影响用户体验”

– alert: HighMemoryUsage
expr: avg(memory_usage) > 200
for: 15m
labels:
severity: warning
annotations:
summary: “内存使用过高”
description: “内存使用超过200MB,可能存在内存泄漏”
六、最佳实践与优化
6.1 性能优化建议
采样率控制
:对性能数据进行采样,避免影响应用性能
// 动态采样率
const dynamicSampleRate = () => {
// 网络状况差时降低采样率
if (navigator.connection?.effectiveType === ‘2g’) {
return 0.1;
}
// 高性能设备保持高采样率
if (navigator.hardwareConcurrency >= 8) {
return 1.0;
}
return 0.5;
};
数据压缩
:减少传输数据量
// 简单的数据压缩
function compressData(data) {
return data.map(item => ({
t: item.timestamp,
u: item.userId,
d: item.type === ‘error’ ? {
m: item.message,
s: item.stack
} : item
}));
}
使用Web Worker
:将数据处理放到后台线程
// monitor-worker.js
self.onmessage = function(e) {
const data = e.data;
// 数据处理逻辑
const processed = processMonitorData(data);
self.postMessage(processed);
};
6.2 隐私与合规
用户同意
:在收集数据前获取用户同意
数据脱敏
:对敏感信息进行脱敏处理
function sanitizeData(data) {
// 移除密码、token等敏感信息
const sanitized = { …data };
if (sanitized.url) {
sanitized.url = sanitized.url.replace(/token=[^&]+/g, ‘token=***’);
}
return sanitized;
}
遵守GDPR/CCPA
:提供数据删除接口
6.3 错误处理边界情况
// 监控SDK自身的错误保护
class SafeMonitorSDK extends MonitorSDK {
report(data, type) {
try {
// 确保数据不会过大
const dataSize = JSON.stringify(data).length;
if (dataSize > 10000) {
data = { message: ‘Data too large’, type };
}

super.report(data, type);
} catch (error) {
// 避免监控SDK自身错误影响业务
console.error(‘Monitor SDK error:’, error);
// 降级:直接console.log
console.log(‘Monitor fallback:’, data);
}
}
}
七、完整集成示例
7.1 项目结构
src/
├── monitor/
│ ├── sdk.js # 核心SDK
│ ├── reactPerformance.js # React性能监控
│ ├── apiMonitor.js # API监控
│ ├── init.js # 初始化
│ └── reporter.js # 上报器
├── components/
│ ├── ErrorBoundary.jsx # 错误边界
│ └── PerformanceWrapper.jsx # 性能包裹组件
├── App.jsx
└── index.js
7.2 完整初始化代码
// src/index.js
import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import App from ‘./App’;
import { initMonitor } from ‘./monitor/init’;
import { interceptFetch, interceptXHR } from ‘./monitor/apiMonitor’;
import { monitorLongTasks } from ‘./monitor/reactPerformance’;

// 初始化监控
initMonitor();

// 拦截网络请求
interceptFetch();
interceptXHR();

// 监控长任务
monitorLongTasks();

// 渲染应用
const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(



);
// src/App.jsx
import React from ‘react’;
import ErrorBoundary from ‘./components/ErrorBoundary’;
import { withPerformanceMonitor } from ‘./monitor/reactPerformance’;

// 使用性能监控的组件
const UserProfile = withPerformanceMonitor(({ userId }) => {
// 组件逻辑
return

User Profile

;
});

function App() {
return (

我的React应用


);
}

export default App;
八、总结
通过本文,我们从零开始搭建了一套完整的React项目监控系统。这套系统涵盖了:
核心SDK
:轻量级、可扩展的监控基础
错误监控
:全局错误、Promise拒绝、React错误边界
性能监控
:页面加载、运行时性能、组件渲染
行为监控
:用户点击、页面可见性
API监控
:请求拦截与性能追踪
数据上报
:批量、压缩、错误重试
可视化
:Grafana集成与告警配置
这套系统的优势在于:
轻量级
:核心SDK代码量小,对应用性能影响最小
可扩展
:模块化设计,易于添加新功能
生产就绪
:包含错误处理、采样控制等生产环境特性
React友好
:深度集成React生态
在实际项目中,你可以根据团队需求选择使用开源方案(如Sentry)或基于本文的自研方案。无论哪种方式,建立完善的监控体系都是保障前端应用质量的关键一步。
记住,监控不是目的,而是手段。最终目标是通过数据驱动的方式,持续优化用户体验,打造高质量的React应用。