@jerfowler/agent-comm-mcp-server
Version:
MCP server for AI agent task communication and delegation with diagnostic lifecycle visibility
243 lines • 9.08 kB
JavaScript
/**
* 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