UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

269 lines (268 loc) 10.1 kB
/** * 错误追踪系统 * 捕获、记录和分析应用程序错误 */ import fs from 'node:fs'; import path from 'node:path'; import pathGuard from '../../shared/PathGuard.js'; import Logger from '../logging/Logger.js'; export class ErrorTracker { criticalErrors; recentErrors; config; errorCounts; errors; reportInterval; constructor(options = {}) { this.config = { logDirectory: options.logDirectory || path.join(pathGuard.projectRoot ?? process.cwd(), '.autosnippet', 'logs', 'errors'), maxErrorsInMemory: options.maxErrorsInMemory || 500, enableFileLogging: options.enableFileLogging !== false, enableConsoleLogging: options.enableConsoleLogging !== false, alertThreshold: options.alertThreshold || 10, // 每分钟错误数阈值 }; this.errors = []; this.errorCounts = new Map(); // 错误类型计数 this.recentErrors = []; // 最近错误 this.criticalErrors = []; // 关键错误 // 确保日志目录存在 if (this.config.enableFileLogging) { this._ensureLogDirectory(); } // 定期生成错误报告(unref 避免阻止进程退出) this.reportInterval = setInterval(() => this._generateReport(), 60000); if (this.reportInterval.unref) { this.reportInterval.unref(); } } /** 确保日志目录存在 */ _ensureLogDirectory() { try { if (!fs.existsSync(this.config.logDirectory)) { fs.mkdirSync(this.config.logDirectory, { recursive: true }); } } catch (error) { Logger.error('创建错误日志目录失败', { error: error.message }); } } /** Express 错误处理中间件 */ errorHandler() { return (err, req, res, _next) => { const errorData = { message: err.message, stack: err.stack, type: err.name || 'UnknownError', statusCode: err.statusCode || 500, route: `${req.method} ${req.path}`, method: req.method, path: req.path, query: req.query, ip: req.ip, userAgent: req.get('user-agent'), timestamp: new Date().toISOString(), severity: (err.statusCode || 500) >= 500 ? 'critical' : 'error', }; this.trackError(errorData); // 发送响应 res.status(errorData.statusCode).json({ success: false, error: { code: err.code || 'INTERNAL_ERROR', message: err.message, ...(process.env.NODE_ENV === 'development' && { stack: err.stack }), }, }); }; } /** 记录错误 */ trackError(errorData) { // 添加到内存 this.errors.push(errorData); if (this.errors.length > this.config.maxErrorsInMemory) { this.errors.shift(); } // 最近错误 this.recentErrors.unshift(errorData); if (this.recentErrors.length > 50) { this.recentErrors.pop(); } // 关键错误 if (errorData.severity === 'critical') { this.criticalErrors.unshift(errorData); if (this.criticalErrors.length > 100) { this.criticalErrors.pop(); } } // 错误类型计数 const errorType = errorData.type; this.errorCounts.set(errorType, (this.errorCounts.get(errorType) || 0) + 1); // 控制台日志 if (this.config.enableConsoleLogging) { if (errorData.severity === 'critical') { Logger.error(`🔴 关键错误: ${errorData.message}`, { route: errorData.route, statusCode: errorData.statusCode, }); } else { Logger.warn(`⚠️ 错误: ${errorData.message}`, { route: errorData.route, }); } } // 文件日志 if (this.config.enableFileLogging) { this._writeToFile(errorData); } // 检查是否需要告警 this._checkAlertThreshold(); } /** 写入文件 */ _writeToFile(errorData) { try { const date = new Date().toISOString().split('T')[0]; const fileName = `errors-${date}.log`; const filePath = path.join(this.config.logDirectory, fileName); const logEntry = `${JSON.stringify({ ...errorData, _timestamp: Date.now(), })}\n`; fs.appendFileSync(filePath, logEntry, 'utf8'); } catch (error) { Logger.error('写入错误日志文件失败', { error: error.message }); } } /** 检查告警阈值 */ _checkAlertThreshold() { const oneMinuteAgo = Date.now() - 60000; const recentErrorCount = this.errors.filter((err) => new Date(err.timestamp).getTime() > oneMinuteAgo).length; if (recentErrorCount >= this.config.alertThreshold) { Logger.error(`🚨 告警: 最近1分钟错误数过高 (${recentErrorCount} 个)`); // 这里可以集成通知服务(邮件、Slack 等) } } /** 生成错误报告 */ _generateReport() { const now = Date.now(); const oneHourAgo = now - 3600000; const recentErrorsCount = this.errors.filter((err) => new Date(err.timestamp).getTime() > oneHourAgo).length; if (recentErrorsCount > 0) { Logger.info('📋 错误报告 (最近1小时)', { totalErrors: recentErrorsCount, criticalErrors: this.criticalErrors.filter((err) => new Date(err.timestamp).getTime() > oneHourAgo).length, topErrorTypes: this._getTopErrorTypes(5), }); } } /** 获取最常见错误类型 */ _getTopErrorTypes(limit = 10) { return Array.from(this.errorCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, limit) .map(([type, count]) => ({ type, count })); } /** 获取错误统计 */ getStats() { const now = Date.now(); const oneHourAgo = now - 3600000; const oneDayAgo = now - 86400000; const lastHourErrors = this.errors.filter((err) => new Date(err.timestamp).getTime() > oneHourAgo); const last24HoursErrors = this.errors.filter((err) => new Date(err.timestamp).getTime() > oneDayAgo); return { summary: { totalErrors: this.errors.length, criticalErrors: this.criticalErrors.length, lastHourErrors: lastHourErrors.length, last24HoursErrors: last24HoursErrors.length, uniqueErrorTypes: this.errorCounts.size, }, topErrorTypes: this._getTopErrorTypes(10), recentErrors: this.recentErrors.slice(0, 10).map((err) => ({ type: err.type, message: err.message, route: err.route, statusCode: err.statusCode, severity: err.severity, timestamp: err.timestamp, })), criticalErrors: this.criticalErrors.slice(0, 10).map((err) => ({ type: err.type, message: err.message, route: err.route, timestamp: err.timestamp, })), errorsByRoute: this._getErrorsByRoute(), }; } /** 按路由统计错误 */ _getErrorsByRoute() { const routeErrors = new Map(); this.errors.forEach((err) => { const route = err.route; routeErrors.set(route, (routeErrors.get(route) || 0) + 1); }); return Array.from(routeErrors.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([route, count]) => ({ route, count })); } /** 清除错误记录 */ clearErrors() { this.errors = []; this.recentErrors = []; this.criticalErrors = []; this.errorCounts.clear(); Logger.info('错误追踪记录已清除'); } /** 搜索错误 */ searchErrors(options = {}) { let results = [...this.errors]; if (options.type) { results = results.filter((err) => err.type === options.type); } if (options.route) { results = results.filter((err) => err.route?.includes(options.route)); } if (options.severity) { results = results.filter((err) => err.severity === options.severity); } if (options.startDate) { const startTime = new Date(options.startDate).getTime(); results = results.filter((err) => new Date(err.timestamp).getTime() >= startTime); } if (options.endDate) { const endTime = new Date(options.endDate).getTime(); results = results.filter((err) => new Date(err.timestamp).getTime() <= endTime); } return results.slice(0, options.limit || 100); } /** 停止错误追踪 */ shutdown() { if (this.reportInterval) { clearInterval(this.reportInterval); } Logger.info('错误追踪已停止'); } } // 单例实例 let errorTrackerInstance = null; /** 初始化错误追踪 */ export function initErrorTracker(options = {}) { if (errorTrackerInstance) { return errorTrackerInstance; } errorTrackerInstance = new ErrorTracker(options); Logger.info('✅ 错误追踪已启用'); return errorTrackerInstance; } /** 获取错误追踪实例 */ export function getErrorTracker() { if (!errorTrackerInstance) { throw new Error('错误追踪未初始化,请先调用 initErrorTracker()'); } return errorTrackerInstance; } export default ErrorTracker;