- 发布于
前端监控与错误处理:异常捕获、性能监控与用户体验优化
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
前端监控与错误处理:异常捕获、性能监控与用户体验优化
前端监控和错误处理是保障用户体验的重要环节。本文将分享前端监控系统的构建经验和错误处理的最佳实践。
错误捕获与处理
全局错误捕获机制
// 全局错误处理器
class GlobalErrorHandler {
constructor(options = {}) {
this.options = {
enableConsoleLog: true,
enableRemoteLog: true,
maxErrors: 50,
reportUrl: '/api/errors',
...options
};
this.errors = [];
this.errorCounts = new Map();
this.init();
}
init() {
// 捕获JavaScript运行时错误
window.addEventListener('error', (event) => {
this.handleError({
type: 'javascript',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error,
stack: event.error?.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
});
// 捕获Promise未处理的rejection
window.addEventListener('unhandledrejection', (event) => {
this.handleError({
type: 'promise',
message: event.reason?.message || 'Unhandled Promise Rejection',
reason: event.reason,
stack: event.reason?.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
});
// 捕获资源加载错误
window.addEventListener('error', (event) => {
if (event.target !== window) {
this.handleError({
type: 'resource',
message: `Failed to load resource: ${event.target.src || event.target.href}`,
element: event.target.tagName,
source: event.target.src || event.target.href,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
});
}
}, true);
// 拦截console.error
this.interceptConsoleError();
// 拦截网络请求错误
this.interceptNetworkErrors();
}
handleError(errorInfo) {
// 错误去重
const errorKey = this.generateErrorKey(errorInfo);
const count = this.errorCounts.get(errorKey) || 0;
this.errorCounts.set(errorKey, count + 1);
// 如果同一错误出现次数过多,降低上报频率
if (count > 10 && count % 10 !== 0) {
return;
}
// 添加额外信息
errorInfo.count = count + 1;
errorInfo.id = this.generateErrorId();
errorInfo.sessionId = this.getSessionId();
errorInfo.userId = this.getUserId();
errorInfo.pageInfo = this.getPageInfo();
errorInfo.deviceInfo = this.getDeviceInfo();
// 存储错误
this.errors.push(errorInfo);
// 限制错误数量
if (this.errors.length > this.options.maxErrors) {
this.errors.shift();
}
// 控制台输出
if (this.options.enableConsoleLog) {
console.error('Global Error Caught:', errorInfo);
}
// 远程上报
if (this.options.enableRemoteLog) {
this.reportError(errorInfo);
}
// 触发自定义事件
this.dispatchErrorEvent(errorInfo);
}
generateErrorKey(errorInfo) {
return `${errorInfo.type}-${errorInfo.message}-${errorInfo.filename}-${errorInfo.lineno}`;
}
generateErrorId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
getSessionId() {
let sessionId = sessionStorage.getItem('sessionId');
if (!sessionId) {
sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2);
sessionStorage.setItem('sessionId', sessionId);
}
return sessionId;
}
getUserId() {
// 从localStorage、cookie或其他地方获取用户ID
return localStorage.getItem('userId') || 'anonymous';
}
getPageInfo() {
return {
url: window.location.href,
title: document.title,
referrer: document.referrer,
timestamp: Date.now()
};
}
getDeviceInfo() {
return {
userAgent: navigator.userAgent,
language: navigator.language,
platform: navigator.platform,
cookieEnabled: navigator.cookieEnabled,
onLine: navigator.onLine,
screen: {
width: screen.width,
height: screen.height,
colorDepth: screen.colorDepth
},
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
};
}
interceptConsoleError() {
const originalError = console.error;
console.error = (...args) => {
this.handleError({
type: 'console',
message: args.join(' '),
args: args,
timestamp: Date.now(),
url: window.location.href,
stack: new Error().stack
});
originalError.apply(console, args);
};
}
interceptNetworkErrors() {
// 拦截fetch错误
const originalFetch = window.fetch;
window.fetch = async (...args) => {
try {
const response = await originalFetch(...args);
if (!response.ok) {
this.handleError({
type: 'network',
message: `HTTP ${response.status}: ${response.statusText}`,
url: args[0],
status: response.status,
statusText: response.statusText,
timestamp: Date.now()
});
}
return response;
} catch (error) {
this.handleError({
type: 'network',
message: `Fetch failed: ${error.message}`,
url: args[0],
error: error,
stack: error.stack,
timestamp: Date.now()
});
throw error;
}
};
// 拦截XMLHttpRequest错误
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url, ...args) {
this._method = method;
this._url = url;
return originalOpen.call(this, method, url, ...args);
};
XMLHttpRequest.prototype.send = function(...args) {
this.addEventListener('error', () => {
globalErrorHandler.handleError({
type: 'network',
message: `XMLHttpRequest failed: ${this._method} ${this._url}`,
method: this._method,
url: this._url,
status: this.status,
statusText: this.statusText,
timestamp: Date.now()
});
});
this.addEventListener('timeout', () => {
globalErrorHandler.handleError({
type: 'network',
message: `XMLHttpRequest timeout: ${this._method} ${this._url}`,
method: this._method,
url: this._url,
timestamp: Date.now()
});
});
return originalSend.call(this, ...args);
};
}
async reportError(errorInfo) {
try {
await fetch(this.options.reportUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(errorInfo)
});
} catch (error) {
console.warn('Failed to report error:', error);
}
}
dispatchErrorEvent(errorInfo) {
const event = new CustomEvent('globalError', {
detail: errorInfo
});
window.dispatchEvent(event);
}
// 手动报告错误
reportCustomError(message, extra = {}) {
this.handleError({
type: 'custom',
message,
...extra,
timestamp: Date.now(),
url: window.location.href,
stack: new Error().stack
});
}
// 获取错误统计
getErrorStats() {
const stats = {
total: this.errors.length,
byType: {},
byPage: {},
recent: this.errors.slice(-10)
};
this.errors.forEach(error => {
stats.byType[error.type] = (stats.byType[error.type] || 0) + 1;
stats.byPage[error.url] = (stats.byPage[error.url] || 0) + 1;
});
return stats;
}
// 清除错误记录
clearErrors() {
this.errors = [];
this.errorCounts.clear();
}
}
// 全局错误处理器实例
const globalErrorHandler = new GlobalErrorHandler({
reportUrl: '/api/frontend-errors',
maxErrors: 100
});
// React错误边界
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({
error,
errorInfo
});
// 报告React错误
globalErrorHandler.reportCustomError(error.message, {
type: 'react',
componentStack: errorInfo.componentStack,
errorBoundary: this.constructor.name,
props: this.props,
stack: error.stack
});
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
// 使用示例
function App() {
return (
<ErrorBoundary fallback={<div>应用出现错误,请刷新页面</div>}>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
</ErrorBoundary>
);
}
异步错误处理
// 异步操作错误处理工具
class AsyncErrorHandler {
constructor() {
this.pendingPromises = new Set();
this.retryConfig = new Map();
}
// 包装Promise以添加错误处理
wrapPromise(promise, options = {}) {
const {
timeout = 10000,
retries = 3,
retryDelay = 1000,
onError = null,
onRetry = null
} = options;
const wrappedPromise = this.addTimeout(promise, timeout)
.catch(error => {
if (retries > 0) {
return this.retryPromise(promise, {
...options,
retries: retries - 1
});
}
if (onError) {
onError(error);
}
globalErrorHandler.reportCustomError(error.message, {
type: 'async',
operation: options.operation || 'unknown',
retries: options.originalRetries || 3,
timeout
});
throw error;
});
this.pendingPromises.add(wrappedPromise);
wrappedPromise.finally(() => {
this.pendingPromises.delete(wrappedPromise);
});
return wrappedPromise;
}
addTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${timeout}ms`));
}, timeout);
})
]);
}
async retryPromise(promiseFactory, options) {
const { retries, retryDelay, onRetry } = options;
await this.delay(retryDelay);
if (onRetry) {
onRetry(options.originalRetries - retries);
}
return this.wrapPromise(
typeof promiseFactory === 'function' ? promiseFactory() : promiseFactory,
options
);
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 安全的异步函数执行器
async safeExecute(asyncFn, options = {}) {
const {
fallback = null,
silent = false,
operation = 'unknown'
} = options;
try {
return await this.wrapPromise(asyncFn(), {
...options,
operation
});
} catch (error) {
if (!silent) {
console.error(`Async operation failed: ${operation}`, error);
}
return fallback;
}
}
// 批量处理异步操作
async batchExecute(operations, options = {}) {
const {
concurrency = 5,
failFast = false,
onProgress = null
} = options;
const results = [];
const errors = [];
for (let i = 0; i < operations.length; i += concurrency) {
const batch = operations.slice(i, i + concurrency);
const batchPromises = batch.map(async (operation, index) => {
try {
const result = await this.safeExecute(operation.fn, {
...operation.options,
operation: operation.name || `operation-${i + index}`
});
if (onProgress) {
onProgress(i + index + 1, operations.length);
}
return { success: true, result, index: i + index };
} catch (error) {
const errorInfo = { success: false, error, index: i + index };
errors.push(errorInfo);
if (failFast) {
throw error;
}
return errorInfo;
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return {
results: results.filter(r => r.success),
errors,
total: operations.length,
successCount: results.filter(r => r.success).length,
errorCount: errors.length
};
}
// 获取待处理的Promise数量
getPendingCount() {
return this.pendingPromises.size;
}
// 等待所有Promise完成
async waitForAll() {
await Promise.allSettled(Array.from(this.pendingPromises));
}
}
// 全局异步错误处理器
const asyncErrorHandler = new AsyncErrorHandler();
// 使用示例
async function fetchUserData(userId) {
return asyncErrorHandler.safeExecute(
() => fetch(`/api/users/${userId}`).then(res => res.json()),
{
timeout: 5000,
retries: 3,
retryDelay: 1000,
operation: 'fetchUserData',
fallback: { id: userId, name: 'Unknown User' },
onRetry: (attempt) => {
console.log(`Retrying fetchUserData, attempt ${attempt}`);
}
}
);
}
// React Hook for async error handling
import { useState, useCallback } from 'react';
function useAsyncError() {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const execute = useCallback(async (asyncFn, options = {}) => {
setLoading(true);
setError(null);
try {
const result = await asyncErrorHandler.safeExecute(asyncFn, options);
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
return { execute, error, loading, clearError };
}
// 使用示例
function UserProfile({ userId }) {
const { execute, error, loading, clearError } = useAsyncError();
const [user, setUser] = useState(null);
const loadUser = useCallback(async () => {
const userData = await execute(
() => fetchUserData(userId),
{ operation: 'loadUserProfile' }
);
setUser(userData);
}, [userId, execute]);
useEffect(() => {
loadUser();
}, [loadUser]);
if (loading) return <div>Loading...</div>;
if (error) return (
<div>
<p>Error: {error.message}</p>
<button onClick={() => { clearError(); loadUser(); }}>
Retry
</button>
</div>
);
return user ? <div>Welcome, {user.name}!</div> : null;
}
性能监控
性能指标收集
// 性能监控器
class PerformanceMonitor {
constructor(options = {}) {
this.options = {
enableAutoReport: true,
reportInterval: 30000, // 30秒
reportUrl: '/api/performance',
...options
};
this.metrics = {
navigation: {},
resources: [],
vitals: {},
custom: {}
};
this.observers = [];
this.init();
}
init() {
// 监控页面加载性能
this.monitorNavigation();
// 监控资源加载性能
this.monitorResources();
// 监控Core Web Vitals
this.monitorWebVitals();
// 监控长任务
this.monitorLongTasks();
// 监控内存使用
this.monitorMemory();
// 自动上报
if (this.options.enableAutoReport) {
this.startAutoReport();
}
}
monitorNavigation() {
if ('performance' in window && 'getEntriesByType' in performance) {
window.addEventListener('load', () => {
setTimeout(() => {
const navigation = performance.getEntriesByType('navigation')[0];
if (navigation) {
this.metrics.navigation = {
// DNS查询时间
dnsLookup: navigation.domainLookupEnd - navigation.domainLookupStart,
// TCP连接时间
tcpConnect: navigation.connectEnd - navigation.connectStart,
// SSL握手时间
sslConnect: navigation.secureConnectionStart > 0
? navigation.connectEnd - navigation.secureConnectionStart
: 0,
// 请求响应时间
request: navigation.responseEnd - navigation.requestStart,
// DOM解析时间
domParse: navigation.domContentLoadedEventEnd - navigation.responseEnd,
// 资源加载时间
resourceLoad: navigation.loadEventEnd - navigation.domContentLoadedEventEnd,
// 总加载时间
totalLoad: navigation.loadEventEnd - navigation.navigationStart,
// 首字节时间
ttfb: navigation.responseStart - navigation.navigationStart,
// DOM准备时间
domReady: navigation.domContentLoadedEventEnd - navigation.navigationStart,
// 页面完全加载时间
pageLoad: navigation.loadEventEnd - navigation.navigationStart
};
}
}, 0);
});
}
}
monitorResources() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource') {
this.metrics.resources.push({
name: entry.name,
type: this.getResourceType(entry.name),
duration: entry.duration,
size: entry.transferSize || 0,
startTime: entry.startTime,
endTime: entry.responseEnd
});
}
}
});
observer.observe({ entryTypes: ['resource'] });
this.observers.push(observer);
}
}
monitorWebVitals() {
// First Contentful Paint (FCP)
this.observePerformanceEntry('paint', (entries) => {
const fcp = entries.find(entry => entry.name === 'first-contentful-paint');
if (fcp) {
this.metrics.vitals.fcp = fcp.startTime;
}
});
// Largest Contentful Paint (LCP)
this.observePerformanceEntry('largest-contentful-paint', (entries) => {
const lcp = entries[entries.length - 1];
if (lcp) {
this.metrics.vitals.lcp = lcp.startTime;
}
});
// First Input Delay (FID)
this.observePerformanceEntry('first-input', (entries) => {
const fid = entries[0];
if (fid) {
this.metrics.vitals.fid = fid.processingStart - fid.startTime;
}
});
// Cumulative Layout Shift (CLS)
let clsValue = 0;
this.observePerformanceEntry('layout-shift', (entries) => {
for (const entry of entries) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
this.metrics.vitals.cls = clsValue;
});
}
monitorLongTasks() {
if ('PerformanceObserver' in window) {
try {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) { // 长任务阈值50ms
this.metrics.custom.longTasks = this.metrics.custom.longTasks || [];
this.metrics.custom.longTasks.push({
duration: entry.duration,
startTime: entry.startTime,
name: entry.name
});
}
}
});
observer.observe({ entryTypes: ['longtask'] });
this.observers.push(observer);
} catch (e) {
// longtask可能不被支持
}
}
}
monitorMemory() {
if ('memory' in performance) {
setInterval(() => {
this.metrics.custom.memory = {
usedJSHeapSize: performance.memory.usedJSHeapSize,
totalJSHeapSize: performance.memory.totalJSHeapSize,
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
timestamp: Date.now()
};
}, 10000); // 每10秒记录一次
}
}
observePerformanceEntry(entryType, callback) {
if ('PerformanceObserver' in window) {
try {
const observer = new PerformanceObserver((list) => {
callback(list.getEntries());
});
observer.observe({ entryTypes: [entryType] });
this.observers.push(observer);
} catch (e) {
console.warn(`Cannot observe ${entryType}:`, e);
}
}
}
getResourceType(url) {
if (url.match(/\.(css)$/)) return 'css';
if (url.match(/\.(js)$/)) return 'js';
if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/)) return 'image';
if (url.match(/\.(woff|woff2|ttf|eot)$/)) return 'font';
return 'other';
}
// 自定义性能标记
mark(name) {
if ('performance' in window && 'mark' in performance) {
performance.mark(name);
}
}
// 测量性能
measure(name, startMark, endMark) {
if ('performance' in window && 'measure' in performance) {
performance.measure(name, startMark, endMark);
const measures = performance.getEntriesByName(name, 'measure');
const measure = measures[measures.length - 1];
if (measure) {
this.metrics.custom[name] = measure.duration;
}
}
}
// 记录自定义指标
recordMetric(name, value, unit = 'ms') {
this.metrics.custom[name] = {
value,
unit,
timestamp: Date.now()
};
}
// 获取性能报告
getPerformanceReport() {
return {
...this.metrics,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
connection: this.getConnectionInfo()
};
}
getConnectionInfo() {
if ('connection' in navigator) {
const conn = navigator.connection;
return {
effectiveType: conn.effectiveType,
downlink: conn.downlink,
rtt: conn.rtt,
saveData: conn.saveData
};
}
return null;
}
// 上报性能数据
async reportPerformance() {
try {
const report = this.getPerformanceReport();
await fetch(this.options.reportUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(report)
});
} catch (error) {
console.warn('Failed to report performance:', error);
}
}
// 开始自动上报
startAutoReport() {
// 页面卸载时上报
window.addEventListener('beforeunload', () => {
this.reportPerformance();
});
// 定期上报
setInterval(() => {
this.reportPerformance();
}, this.options.reportInterval);
}
// 清理观察器
destroy() {
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
}
}
// 全局性能监控器
const performanceMonitor = new PerformanceMonitor({
reportUrl: '/api/performance-metrics'
});
// 页面卸载时清理
window.addEventListener('beforeunload', () => {
performanceMonitor.destroy();
});
// 使用示例
// 标记关键操作开始
performanceMonitor.mark('api-call-start');
// 执行API调用
fetch('/api/data')
.then(response => response.json())
.then(data => {
// 标记操作结束
performanceMonitor.mark('api-call-end');
// 测量耗时
performanceMonitor.measure('api-call-duration', 'api-call-start', 'api-call-end');
// 记录自定义指标
performanceMonitor.recordMetric('data-size', JSON.stringify(data).length, 'bytes');
});
用户体验监控
用户行为追踪
// 用户体验监控器
class UXMonitor {
constructor(options = {}) {
this.options = {
trackClicks: true,
trackScrolls: true,
trackPageViews: true,
trackFormInteractions: true,
reportUrl: '/api/ux-metrics',
...options
};
this.sessions = {
startTime: Date.now(),
pageViews: [],
interactions: [],
scrollDepth: 0,
timeOnPage: 0
};
this.init();
}
init() {
if (this.options.trackPageViews) {
this.trackPageView();
}
if (this.options.trackClicks) {
this.trackClicks();
}
if (this.options.trackScrolls) {
this.trackScrollBehavior();
}
if (this.options.trackFormInteractions) {
this.trackFormInteractions();
}
this.trackTimeOnPage();
this.trackVisibilityChanges();
}
trackPageView() {
const pageView = {
url: window.location.href,
title: document.title,
referrer: document.referrer,
timestamp: Date.now(),
loadTime: performance.now()
};
this.sessions.pageViews.push(pageView);
}
trackClicks() {
document.addEventListener('click', (event) => {
const target = event.target;
const interaction = {
type: 'click',
element: target.tagName.toLowerCase(),
className: target.className,
id: target.id,
text: target.textContent?.slice(0, 100),
x: event.clientX,
y: event.clientY,
timestamp: Date.now()
};
this.sessions.interactions.push(interaction);
});
}
trackScrollBehavior() {
let maxScrollDepth = 0;
let scrollTimer;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollDepth = Math.round((scrollTop + windowHeight) / documentHeight * 100);
if (scrollDepth > maxScrollDepth) {
maxScrollDepth = scrollDepth;
this.sessions.scrollDepth = maxScrollDepth;
// 记录滚动里程碑
if (scrollDepth >= 25 && scrollDepth < 50 && maxScrollDepth >= 25) {
this.recordScrollMilestone(25);
} else if (scrollDepth >= 50 && scrollDepth < 75 && maxScrollDepth >= 50) {
this.recordScrollMilestone(50);
} else if (scrollDepth >= 75 && scrollDepth < 100 && maxScrollDepth >= 75) {
this.recordScrollMilestone(75);
} else if (scrollDepth >= 100 && maxScrollDepth >= 100) {
this.recordScrollMilestone(100);
}
}
}, 100);
});
}
recordScrollMilestone(depth) {
this.sessions.interactions.push({
type: 'scroll',
depth: depth,
timestamp: Date.now()
});
}
trackFormInteractions() {
// 表单焦点事件
document.addEventListener('focusin', (event) => {
if (event.target.matches('input, textarea, select')) {
this.sessions.interactions.push({
type: 'form_focus',
element: event.target.tagName.toLowerCase(),
name: event.target.name,
id: event.target.id,
timestamp: Date.now()
});
}
});
// 表单提交事件
document.addEventListener('submit', (event) => {
const form = event.target;
const formData = new FormData(form);
const fields = Array.from(formData.keys());
this.sessions.interactions.push({
type: 'form_submit',
formId: form.id,
formClass: form.className,
fieldCount: fields.length,
fields: fields,
timestamp: Date.now()
});
});
// 表单验证错误
document.addEventListener('invalid', (event) => {
this.sessions.interactions.push({
type: 'form_error',
element: event.target.tagName.toLowerCase(),
name: event.target.name,
validationMessage: event.target.validationMessage,
timestamp: Date.now()
});
});
}
trackTimeOnPage() {
this.startTime = Date.now();
// 页面卸载时记录总时间
window.addEventListener('beforeunload', () => {
this.sessions.timeOnPage = Date.now() - this.startTime;
});
// 定期更新时间
setInterval(() => {
this.sessions.timeOnPage = Date.now() - this.startTime;
}, 10000);
}
trackVisibilityChanges() {
let hiddenTime = 0;
let lastHiddenTime = 0;
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
lastHiddenTime = Date.now();
} else {
if (lastHiddenTime > 0) {
hiddenTime += Date.now() - lastHiddenTime;
}
}
this.sessions.interactions.push({
type: 'visibility_change',
hidden: document.hidden,
totalHiddenTime: hiddenTime,
timestamp: Date.now()
});
});
}
// 记录自定义用户行为
trackCustomEvent(eventName, data = {}) {
this.sessions.interactions.push({
type: 'custom',
eventName,
data,
timestamp: Date.now()
});
}
// 获取用户体验报告
getUXReport() {
return {
...this.sessions,
sessionDuration: Date.now() - this.sessions.startTime,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
};
}
// 上报用户体验数据
async reportUX() {
try {
const report = this.getUXReport();
await fetch(this.options.reportUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(report)
});
} catch (error) {
console.warn('Failed to report UX metrics:', error);
}
}
}
// 全局用户体验监控器
const uxMonitor = new UXMonitor();
// 页面卸载时上报
window.addEventListener('beforeunload', () => {
uxMonitor.reportUX();
});
// 定期上报
setInterval(() => {
uxMonitor.reportUX();
}, 60000); // 每分钟上报一次
// 使用示例
// 追踪自定义事件
uxMonitor.trackCustomEvent('video_play', {
videoId: 'video-123',
duration: 120,
quality: '1080p'
});
uxMonitor.trackCustomEvent('search', {
query: 'javascript tutorial',
resultsCount: 25
});
总结
前端监控与错误处理的核心要点:
- 全局错误捕获:JavaScript错误、Promise rejection、资源加载错误
- 异步错误处理:超时控制、重试机制、降级策略
- 性能监控:Core Web Vitals、资源加载、自定义指标
- 用户体验追踪:用户行为、交互数据、页面停留时间
- 数据上报:实时上报、批量上报、离线缓存
建立完善的前端监控体系能够帮助我们及时发现问题、优化用户体验,是现代Web应用不可或缺的基础设施。