本文目录导读:

我来帮你编写一个全局捕获异步异常信息的脚本,这里提供几种常见场景的实现方案:
浏览器环境 - 全局错误捕获
// global-error-handler.js
class GlobalErrorHandler {
constructor() {
this.init();
}
init() {
// 捕获未处理的Promise异常
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise拒绝:', {
reason: event.reason,
promise: event.promise,
time: new Date().toISOString()
});
// 记录错误信息
this.logError({
type: 'unhandledrejection',
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
timestamp: new Date().toISOString()
});
// 阻止默认处理
event.preventDefault();
});
// 捕获全局错误
window.addEventListener('error', (event) => {
// 过滤资源加载错误
if (event.target && (event.target.tagName || event.target.src)) {
return;
}
console.error('全局错误:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error,
time: new Date().toISOString()
});
this.logError({
type: 'global_error',
message: event.message,
stack: event.error?.stack,
filename: event.filename,
line: event.lineno,
column: event.colno,
timestamp: new Date().toISOString()
});
event.preventDefault();
});
// 重写console.error以便控制台错误
const originalConsoleError = console.error;
console.error = (...args) => {
this.logError({
type: 'console_error',
message: args.map(arg => String(arg)).join(' '),
timestamp: new Date().toISOString()
});
originalConsoleError.apply(console, args);
};
}
logError(errorInfo) {
// 这里可以发送到服务器或存储到本地
console.log('错误日志:', errorInfo);
// 示例:发送到服务器
// this.sendToServer(errorInfo);
// 示例:存储到localStorage
// this.saveToLocal(errorInfo);
}
sendToServer(errorInfo) {
fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorInfo)
}).catch(e => console.error('日志发送失败:', e));
}
saveToLocal(errorInfo) {
const errors = JSON.parse(localStorage.getItem('error_logs') || '[]');
errors.push(errorInfo);
// 只保留最近100条
if (errors.length > 100) errors.shift();
localStorage.setItem('error_logs', JSON.stringify(errors));
}
}
// 使用
const errorHandler = new GlobalErrorHandler();
Node.js 环境 - 全局异步错误捕获
// node-global-error-handler.js
class NodeGlobalErrorHandler {
constructor(options = {}) {
this.options = {
exitOnError: false,
logToFile: true,
logPath: './error.log',
...options
};
this.init();
}
init() {
// 捕获未处理的Promise异常
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', {
reason: reason instanceof Error ? reason.message : reason,
stack: reason instanceof Error ? reason.stack : new Error().stack,
time: new Date().toISOString()
});
this.logError({
type: 'UNHANDLED_REJECTION',
message: reason instanceof Error ? reason.message : String(reason),
stack: reason instanceof Error ? reason.stack : undefined,
timestamp: new Date().toISOString()
});
// 可选择优雅退出
if (this.options.exitOnError) {
process.exit(1);
}
});
// 捕获未捕获的异常
process.on('uncaughtException', (error) => {
console.error('未捕获的异常:', {
message: error.message,
stack: error.stack,
time: new Date().toISOString()
});
this.logError({
type: 'UNCAUGHT_EXCEPTION',
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
if (this.options.exitOnError) {
process.exit(1);
}
});
// 监听警告
process.on('warning', (warning) => {
console.warn('警告:', warning.message);
this.logError({
type: 'WARNING',
message: warning.message,
stack: warning.stack,
timestamp: new Date().toISOString()
});
});
}
logError(errorInfo) {
const logEntry = JSON.stringify(errorInfo) + '\n';
if (this.options.logToFile) {
const fs = require('fs');
fs.appendFile(this.options.logPath, logEntry, (err) => {
if (err) console.error('写入日志文件失败:', err);
});
}
// 可以添加其他日志存储方式
console.log('错误日志:', errorInfo);
}
}
// 使用
const errorHandler = new NodeGlobalErrorHandler({
exitOnError: false,
logPath: './errors.log'
});
通用异步错误封装
// async-error-wrapper.js
class AsyncErrorWrapper {
// 包装异步函数
static wrapAsync(fn) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
this.handleError(error, { functionName: fn.name, args });
throw error; // 重新抛出或返回默认值
}
};
}
// 包装Promise
static wrapPromise(promise, errorHandler = null) {
return promise.catch(error => {
if (errorHandler) {
return errorHandler(error);
}
this.handleError(error);
return null; // 返回默认值
});
}
// 处理错误
static handleError(error, context = {}) {
console.error('异步错误捕获:', {
message: error.message,
stack: error.stack,
time: new Date().toISOString(),
...context
});
// 可以添加自定义错误处理逻辑
// this.sendToServer({ ... });
}
// 重试机制
static async retry(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
console.warn(`第 ${i + 1} 次重试失败:`, error.message);
if (i === maxRetries - 1) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
}
// 使用示例
const safeAsyncFunction = AsyncErrorWrapper.wrapAsync(async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
});
// 或者包装Promise
const safePromise = AsyncErrorWrapper.wrapPromise(
fetch('https://api.example.com/data'),
(error) => {
console.log('友好的错误处理:', error.message);
return { error: true, message: '请求失败' };
}
);
// 带重试的调用
AsyncErrorWrapper.retry(
async () => fetch('https://api.example.com/data'),
3,
2000
);
完整的生产环境示例
// production-error-handler.js
class ProductionErrorHandler {
constructor(config = {}) {
this.config = {
logLevel: process.env.NODE_ENV === 'production' ? 'error' : 'warn',
sampleRate: 1, // 采样率
maxErrors: 100,
...config
};
this.errorCount = 0;
this.errors = [];
this.init();
}
init() {
this.setupGlobalHandlers();
this.setupFrameworkIntegration();
}
setupGlobalHandlers() {
// 浏览器
if (typeof window !== 'undefined') {
this.setupBrowserHandlers();
}
// Node.js
else {
this.setupNodeHandlers();
}
}
setupBrowserHandlers() {
window.onerror = (message, source, lineno, colno, error) => {
this.captureError('WINDOW_ERROR', {
message,
source,
lineno,
colno,
stack: error?.stack
});
};
window.onunhandledrejection = (event) => {
this.captureError('UNHANDLED_REJECTION', {
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack
});
};
}
setupNodeHandlers() {
process.on('uncaughtException', (error) => {
this.captureError('UNCAUGHT_EXCEPTION', {
message: error.message,
stack: error.stack
});
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
this.captureError('UNHANDLED_REJECTION', {
message: reason instanceof Error ? reason.message : String(reason),
stack: reason instanceof Error ? reason.stack : undefined
});
});
}
captureError(type, data) {
if (this.errorCount >= this.config.maxErrors) return;
if (Math.random() > this.config.sampleRate) return;
const errorEntry = {
type,
timestamp: new Date().toISOString(),
url: typeof window !== 'undefined' ? window.location.href : process.cwd(),
userAgent: navigator?.userAgent,
...data
};
this.errors.push(errorEntry);
this.errorCount++;
this.sendToAnalytics(errorEntry);
this.logToConsole(errorEntry);
}
sendToAnalytics(errorEntry) {
// 发送到错误监控服务
fetch('/api/error-tracking', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorEntry)
}).catch(() => {});
}
logToConsole(entry) {
if (this.config.logLevel === 'error') {
console.error('[ErrorHandler]', entry.type, entry.message);
} else {
console.warn('[ErrorHandler]', entry);
}
}
getErrors() {
return this.errors;
}
clearErrors() {
this.errors = [];
this.errorCount = 0;
}
}
// 使用
const errorHandler = new ProductionErrorHandler({
logLevel: 'error',
sampleRate: 0.5,
maxErrors: 1000
});
使用建议
-
不同环境选择不同方案:
- 浏览器端:使用
window.onerror和unhandledrejection - Node.js:使用
process.on事件监听
- 浏览器端:使用
-
错误分级处理:
- 致命错误:立即处理和报警
- 一般错误:记录日志
- 警告信息:只记录不报警
-
性能优化:
- 使用采样率减少性能影响
- 限制错误数量避免内存泄漏
- 异步发送错误日志
-
安全考虑:
- 不要直接暴露错误堆栈给用户
- 敏感信息脱敏处理
- 设置合理的日志级别
-
集成方案:
- 结合 Sentry、TrackJS 等服务
- 自定义日志服务器
- 本地日志文件管理
这些脚本可以根据你的具体需求进行调整和扩展。