JavaScript 错误监控
在开发Web应用时,错误是不可避免的。然而,及时发现并修复这些错误对于维持良好的用户体验至关重要。JavaScript错误监控系统让我们能够在生产环境中追踪和分析错误,从而更快地定位和解决问题。
为什么需要错误监控?
在没有错误监控的情况下,当用户遇到问题时,他们可能只会看到一个白屏或者功能失效,而开发人员却没有任何线索。错误监控系统提供了一种机制,让我们能够:
- 实时获取生产环境中的错误信息
- 了解错误的发生频率和影响范围
- 收集错误发生时的上下文信息
- 优先修复影响最大的问题
JavaScript 错误监控的基本方法
1. 使用全局错误处理
JavaScript提供了window.onerror
事件处理程序,可以捕获大部分未被try-catch捕获的错误。
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到错误:', {
message, // 错误信息
source, // 发生错误的脚本URL
lineno, // 行号
colno, // 列号
error // Error对象
});
// 向服务器发送错误信息
sendErrorToServer({
message,
source,
lineno,
colno,
stack: error && error.stack
});
// 返回true可以阻止浏览器默认的错误处理(如在控制台中显示错误)
return true;
};
2. 捕获Promise错误
未被处理的Promise拒绝不会被window.onerror
捕获,我们需要使用unhandledrejection
事件:
window.addEventListener('unhandledrejection', function(event) {
console.log('未处理的Promise拒绝:', event.reason);
// 向服务器发送错误信息
sendErrorToServer({
type: 'unhandledrejection',
reason: String(event.reason),
stack: event.reason && event.reason.stack
});
// 阻止默认处理
event.preventDefault();
});
3. 监控网络请求错误
对于AJAX/Fetch请求,我们可以拦截这些请求来监控网络错误:
// 拦截XMLHttpRequest
const originalXhrSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
this.addEventListener('error', function() {
sendErrorToServer({
type: 'xhr_error',
url: this._url,
method: this._method
});
});
this.addEventListener('timeout', function() {
sendErrorToServer({
type: 'xhr_timeout',
url: this._url,
method: this._method
});
});
return originalXhrSend.apply(this, arguments);
};
// 保存原始的open方法
const originalXhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
this._url = url;
this._method = method;
return originalXhrOpen.apply(this, arguments);
};
// 拦截Fetch API
const originalFetch = window.fetch;
window.fetch = function(url, options) {
return originalFetch(url, options)
.catch(error => {
sendErrorToServer({
type: 'fetch_error',
url: url,
error: String(error)
});
throw error;
});
};
4. 监控资源加载错误
使用error
事件可以捕获资源(如图片、脚本、样式表等)加载失败的情况:
window.addEventListener('error', function(event) {
// 过滤资源加载错误
if (event.target && (event.target.src || event.target.href)) {
sendErrorToServer({
type: 'resource_error',
resource: event.target.src || event.target.href,
tagName: event.target.tagName
});
}
}, true); // 注意这里的 true,表示在捕获阶段处理
构建完整的错误监控系统
一个完整的错误监控系统通常需要包含以下组件:
错误上报函数
以下是一个简单的错误上报函数示例:
function sendErrorToServer(errorInfo) {
// 添加一些上下文信息
const reportData = {
...errorInfo,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().getTime(),
// 可以添加用户ID等信息
userId: getUserId()
};
// 使用sendBeacon API进行上报,这样即使页面关闭也能保证数据发送
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/error-report', JSON.stringify(reportData));
} else {
// 降级处理,使用普通的AJAX请求
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/error-report', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(reportData));
}
// 可以选择在本地存储一些错误信息,以便下次启动时上报
try {
const storedErrors = JSON.parse(localStorage.getItem('pendingErrors') || '[]');
storedErrors.push(reportData);
// 限制存储的错误数量
if (storedErrors.length > 10) storedErrors.shift();
localStorage.setItem('pendingErrors', JSON.stringify(storedErrors));
} catch (e) {
// 处理localStorage可能的异常
}
}
错误去重与采样
为了避免相同错误重复上报和减轻服务器负担,我们可以实现错误去重和采样:
// 存储已经上报的错误哈希
const reportedErrors = new Set();
function hashError(error) {
// 简单的哈希函数,实际应用中可能需要更复杂的逻辑
const key = `${error.message}-${error.lineno}-${error.colno}-${error.source}`;
return key;
}
function shouldReportError(error) {
const errorHash = hashError(error);
// 已经上报过的相同错误,不再上报
if (reportedErrors.has(errorHash)) {
return false;
}
// 记录这个错误已经上报
reportedErrors.add(errorHash);
// 控制Set大小,防止内存泄漏
if (reportedErrors.size > 100) {
// 移除最早添加的项
reportedErrors.delete(reportedErrors.keys().next().value);
}
// 错误采样,比如只上报10%的错误
return Math.random() < 0.1;
}