错误监控
错误监控概述
前端错误监控是保障应用稳定性的重要手段,主要包括:
- 错误捕获:JavaScript 错误、资源加载错误
- 错误上报:发送到监控平台
- 错误分析:统计、分析、定位问题
错误类型
1. JavaScript 错误
语法错误:
// 语法错误,无法捕获
const x = ; // SyntaxError
运行时错误:
// 运行时错误,可以捕获
undefined.foo(); // TypeError
异步错误:
// Promise 错误
Promise.reject('error').catch(err => {
console.error(err);
});
// async/await 错误
async function test() {
try {
await Promise.reject('error');
} catch (err) {
console.error(err);
}
}
2. 资源加载错误
图片加载错误:
img.onerror = function() {
console.error('Image load failed');
};
脚本加载错误:
script.onerror = function() {
console.error('Script load failed');
};
3. 网络错误
AJAX 错误:
fetch('/api/data')
.then(response => {
if (!response.ok) {
throw new Error('Network error');
}
return response.json();
})
.catch(error => {
console.error('Fetch error:', error);
});
错误捕获
1. window.onerror
全局错误捕获:
window.onerror = function(message, source, lineno, colno, error) {
console.error('Error:', {
message, // 错误信息
source, // 错误文件
lineno, // 行号
colno, // 列号
error, // 错误对象
});
// 上报错误
reportError({
message,
source,
lineno,
colno,
stack: error?.stack,
});
return true; // 阻止默认错误处理
};
限制:
- 无法捕获 Promise 错误
- 无法捕获资源加载错误
- 跨域脚本错误信息不完整
2. unhandledrejection
Promise 错误捕获:
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled rejection:', event.reason);
// 上报错误
reportError({
type: 'unhandledrejection',
reason: event.reason,
stack: event.reason?.stack,
});
event.preventDefault(); // 阻止默认错误处理
});
3. 资源加载错误
捕获资源加载错误:
window.addEventListener('error', (event) => {
// 区分资源加载错误和 JS 错误
if (event.target !== window) {
console.error('Resource load error:', {
tag: event.target.tagName,
url: event.target.src || event.target.href,
});
// 上报错误
reportError({
type: 'resource',
tag: event.target.tagName,
url: event.target.src || event.target.href,
});
return true;
}
}, true); // 使用捕获阶段
4. Vue 错误捕获
全局错误处理:
Vue.config.errorHandler = (err, vm, info) => {
console.error('Vue error:', {
error: err,
component: vm,
info,
});
reportError({
type: 'vue',
error: err.toString(),
stack: err.stack,
info,
});
};
5. React 错误捕获
Error Boundary:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('React error:', error, errorInfo);
reportError({
type: 'react',
error: error.toString(),
stack: error.stack,
componentStack: errorInfo.componentStack,
});
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
错误上报
1. 基础上报
简单上报:
function reportError(errorInfo) {
const data = {
...errorInfo,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now(),
};
// 使用 sendBeacon(页面卸载时也能发送)
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/error', JSON.stringify(data));
} else {
// 降级方案:使用 Image
const img = new Image();
img.src = `/api/error?data=${encodeURIComponent(JSON.stringify(data))}`;
}
}
2. 批量上报
收集错误,批量上报:
class ErrorCollector {
constructor() {
this.errors = [];
this.timer = null;
}
collect(error) {
this.errors.push({
...error,
timestamp: Date.now(),
});
// 达到阈值立即上报
if (this.errors.length >= 10) {
this.report();
} else {
// 延迟上报
this.scheduleReport();
}
}
scheduleReport() {
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
this.report();
}, 5000); // 5秒后上报
}
report() {
if (this.errors.length === 0) return;
const data = this.errors.slice();
this.errors = [];
fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}).catch(err => {
console.error('Report failed:', err);
// 失败后重新加入队列
this.errors.unshift(...data);
});
}
}
3. Source Map
使用 Source Map 定位错误:
function reportError(errorInfo) {
// 发送错误信息
fetch('/api/error', {
method: 'POST',
body: JSON.stringify({
...errorInfo,
// 包含 Source Map 信息
sourceMap: true,
}),
});
}
// 服务端使用 source-map 库解析
// const sourceMap = require('source-map');
// const consumer = await new sourceMap.SourceMapConsumer(map);
// const original = consumer.originalPositionFor({ line, column });
监控平台
1. Sentry
集成:
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'https://xxx@sentry.io/xxx',
environment: 'production',
beforeSend(event) {
// 过滤敏感信息
if (event.user) {
delete event.user.email;
}
return event;
},
});
// 手动上报
Sentry.captureException(error);
2. 自建监控
服务端接口:
// Express
app.post('/api/error', (req, res) => {
const errorInfo = req.body;
// 存储错误
saveError(errorInfo);
// 发送告警
if (isCritical(errorInfo)) {
sendAlert(errorInfo);
}
res.json({ success: true });
});
错误分析
1. 错误统计
统计维度:
- 错误类型
- 错误频率
- 影响用户数
- 错误趋势
2. 错误分组
分组规则:
- 错误信息相同
- 错误堆栈相同
- 错误文件相同
3. 错误定位
定位信息:
- 错误文件
- 错误行号
- 错误堆栈
- 用户操作路径
最佳实践
1. 错误捕获
- 全局错误捕获
- Promise 错误捕获
- 资源加载错误捕获
- 框架错误捕获
2. 错误上报
- 使用 sendBeacon
- 批量上报
- 失败重试
- 过滤敏感信息
3. 错误分析
- 错误统计
- 错误分组
- 趋势分析
- 告警机制
4. 用户体验
- 错误提示
- 降级方案
- 错误恢复
- 用户反馈