UNPKG

@jerfowler/agent-comm-mcp-server

Version:

MCP server for AI agent task communication and delegation with diagnostic lifecycle visibility

243 lines 9.08 kB
/** * ErrorLogger - Enhanced error tracking and analysis for MCP Server * Captures, categorizes, and analyzes all system errors for improvement */ import { EventLogger } from './EventLogger.js'; import * as fs from '../utils/fs-extra-safe.js'; import path from 'path'; import debug from 'debug'; const log = debug('agent-comm:logging:error'); export class ErrorLogger extends EventLogger { errorLogPath; constructor(baseDir, timerDependency) { super(baseDir, timerDependency); this.errorLogPath = path.join(this.getLogDirectory(), 'error.log'); log('ErrorLogger initialized with error log: %s', this.errorLogPath); } /** * Get the error log file path */ getErrorLogPath() { return this.errorLogPath; } /** * Log an error entry to both error.log and agent-comm.log */ async logError(entry) { log('Logging error: %s - %s', entry.operation, entry.error.message); // Ensure log directory exists await fs.ensureDir(this.getLogDirectory()); // Write to error.log const errorLine = JSON.stringify(entry) + '\n'; await fs.appendFile(this.errorLogPath, errorLine); // Build LogEntry with proper optional handling for exactOptionalPropertyTypes const logEntry = { timestamp: entry.timestamp, operation: `error:${entry.operation}`, agent: entry.agent, success: false, duration: 0, error: { message: entry.error.message, name: entry.error.name, ...(entry.error.stack !== undefined ? { stack: entry.error.stack } : {}), ...(entry.error.code !== undefined ? { code: typeof entry.error.code === 'number' ? String(entry.error.code) : entry.error.code } : {}) }, metadata: { ...entry.metadata, source: entry.source, severity: entry.severity, context: entry.context }, ...(entry.taskId !== undefined ? { taskId: entry.taskId } : {}) }; await this.logOperation(logEntry); } /** * Analyze error patterns across all logged errors */ async analyzeErrorPatterns() { const errors = await this.getAllErrors(); const analysis = { totalErrors: errors.length, errorsBySource: {}, errorsBySeverity: {}, commonPatterns: [], agentErrorRates: {} }; if (errors.length === 0) { return analysis; } // Count by source for (const error of errors) { analysis.errorsBySource[error.source] = (analysis.errorsBySource[error.source] ?? 0) + 1; analysis.errorsBySeverity[error.severity] = (analysis.errorsBySeverity[error.severity] ?? 0) + 1; } // Find common patterns const patternMap = new Map(); for (const error of errors) { const pattern = `${error.error.name}: ${error.error.message}`; const existing = patternMap.get(pattern); if (existing) { existing.count++; if (error.timestamp > existing.lastOccurrence) { existing.lastOccurrence = error.timestamp; } } else { patternMap.set(pattern, { count: 1, lastOccurrence: error.timestamp }); } } // Convert to array and sort by frequency analysis.commonPatterns = Array.from(patternMap.entries()) .map(([pattern, data]) => ({ pattern, frequency: data.count, lastOccurrence: data.lastOccurrence })) .sort((a, b) => b.frequency - a.frequency); // Calculate agent error rates const agentErrors = new Map(); for (const error of errors) { const existing = agentErrors.get(error.agent) ?? []; existing.push(error); agentErrors.set(error.agent, existing); } for (const [agent, agentErrorList] of agentErrors.entries()) { const errorTypes = new Map(); for (const error of agentErrorList) { const type = error.error.name; errorTypes.set(type, (errorTypes.get(type) ?? 0) + 1); } const mostCommon = Array.from(errorTypes.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 3) .map(([type]) => type); analysis.agentErrorRates[agent] = { totalErrors: agentErrorList.length, errorRate: agentErrorList.length / errors.length, mostCommonErrors: mostCommon }; } return analysis; } /** * Get errors for a specific agent */ async getErrorsByAgent(agent) { const errors = await this.getAllErrors(); return errors.filter(e => e.agent === agent); } /** * Get errors by severity level */ async getErrorsBySeverity(severity) { const errors = await this.getAllErrors(); return errors.filter(e => e.severity === severity); } /** * Get errors within a time range */ async getErrorsByTimeRange(start, end) { const errors = await this.getAllErrors(); return errors.filter(e => { const timestamp = new Date(e.timestamp); return timestamp >= start && timestamp <= end; }); } /** * Analyze error patterns across all logged errors */ async analyzeErrors() { return this.analyzeErrorPatterns(); } /** * Get common error patterns */ async getErrorPatterns() { const analysis = await this.analyzeErrorPatterns(); return analysis.commonPatterns; } /** * Get error rates by agent */ async getAgentErrorRates() { const analysis = await this.analyzeErrorPatterns(); return analysis.agentErrorRates; } /** * Generate a comprehensive error report */ async generateErrorReport() { const analysis = await this.analyzeErrorPatterns(); const now = new Date().toISOString(); let report = `# Error Report\n`; report += `Generated: ${now}\n\n`; report += `## Summary\n`; report += `Total Errors: ${analysis.totalErrors}\n\n`; report += `## Errors by Source\n`; for (const [source, count] of Object.entries(analysis.errorsBySource)) { report += `- ${source}: ${count}\n`; } report += '\n'; report += `## Errors by Severity\n`; for (const [severity, count] of Object.entries(analysis.errorsBySeverity)) { report += `- ${severity}: ${count}\n`; } report += '\n'; report += `## Common Patterns\n`; for (const pattern of analysis.commonPatterns.slice(0, 10)) { report += `- "${pattern.pattern}" (${pattern.frequency} occurrences)\n`; } report += '\n'; report += `## Agent Error Rates\n`; for (const [agent, stats] of Object.entries(analysis.agentErrorRates)) { report += `### ${agent}\n`; report += `- Total Errors: ${stats.totalErrors}\n`; report += `- Error Rate: ${(stats.errorRate * 100).toFixed(1)}%\n`; report += `- Most Common: ${stats.mostCommonErrors.join(', ')}\n\n`; } return report; } /** * Clear error entries older than specified days */ async clearOldErrors(retentionDays) { const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000); const errors = await this.getAllErrors(); const recentErrors = errors.filter(e => new Date(e.timestamp) >= cutoffDate); const removedCount = errors.length - recentErrors.length; if (removedCount > 0) { // Write back only recent errors const content = recentErrors.map(e => JSON.stringify(e)).join('\n') + (recentErrors.length > 0 ? '\n' : ''); await fs.writeFile(this.errorLogPath, content); } return removedCount; } /** * Get all error entries from error.log */ async getAllErrors() { if (!(await fs.pathExists(this.errorLogPath))) { return []; } const content = await fs.readFile(this.errorLogPath, 'utf8'); const lines = content.trim().split('\n').filter(line => line.trim()); const errors = []; for (const line of lines) { try { const entry = JSON.parse(line); entry.timestamp = new Date(entry.timestamp); errors.push(entry); } catch (error) { // Skip malformed lines silently - already logged in EventLogger void error; } } return errors; } } //# sourceMappingURL=ErrorLogger.js.map