import { ErrorReport, ErrorAnalysis } from './ErrorDetector.js';
import { ParsedError } from '../types/index.js';
import { ExtendedErrorPattern } from '../patterns/ErrorPatterns.js';
export interface ReportingOptions {
includeStackTraces?: boolean;
includeContext?: boolean;
includeRemediation?: boolean;
maxContextLines?: number;
format?: 'json' | 'markdown' | 'html' | 'plain';
severity?: 'low' | 'medium' | 'high' | 'critical';
categories?: string[];
languages?: string[];
}
export interface MonitoringIntegration {
type: 'webhook' | 'file' | 'console' | 'custom';
endpoint?: string;
filePath?: string;
customHandler?: (report: ErrorReport) => Promise<void>;
threshold?: {
criticalErrors?: number;
totalErrors?: number;
severityScore?: number;
};
}
export interface AlertConfig {
enabled: boolean;
channels: ('email' | 'slack' | 'webhook' | 'console')[];
threshold: {
critical: number;
high: number;
total: number;
};
cooldownMinutes: number;
}
/**
* Error reporting and formatting utilities for structured output and monitoring integration
*/
export class ErrorReporter {
private alertConfig: AlertConfig;
private lastAlertTime = new Map<string, Date>();
constructor(alertConfig?: AlertConfig) {
this.alertConfig = alertConfig || {
enabled: false,
channels: ['console'],
threshold: {
critical: 1,
high: 3,
total: 10,
},
cooldownMinutes: 15,
};
}
/**
* Format error report for console output
*/
formatForConsole(
report: ErrorReport,
options: ReportingOptions = {}
): string {
const lines: string[] = [];
const { includeContext = false, includeRemediation = true } = options;
// Header
lines.push('═══════════════════════════════════════════════════════════');
lines.push(`🔍 ERROR ANALYSIS REPORT`);
lines.push(`📅 Generated: ${new Date().toISOString()}`);
lines.push('═══════════════════════════════════════════════════════════');
lines.push('');
// Summary
lines.push('📊 SUMMARY');
lines.push('───────────────────────────────────────────────────────────');
lines.push(`Total Lines Analyzed: ${report.summary.totalLines}`);
lines.push(`Errors Found: ${report.summary.errorsFound}`);
lines.push('');
// Severity breakdown
lines.push('⚠️ SEVERITY BREAKDOWN');
lines.push(` Critical: ${report.summary.severityBreakdown.critical}`);
lines.push(` High: ${report.summary.severityBreakdown.high}`);
lines.push(` Medium: ${report.summary.severityBreakdown.medium}`);
lines.push(` Low: ${report.summary.severityBreakdown.low}`);
lines.push('');
// Category breakdown
if (Object.keys(report.summary.categoryBreakdown).length > 0) {
lines.push('📂 CATEGORY BREAKDOWN');
Object.entries(report.summary.categoryBreakdown).forEach(
([category, count]) => {
lines.push(
` ${this.getCategoryIcon(category)} ${category}: ${count}`
);
}
);
lines.push('');
}
// Recommendations
if (report.recommendations.length > 0) {
lines.push('💡 RECOMMENDATIONS');
lines.push('───────────────────────────────────────────────────────────');
report.recommendations.forEach((rec) => lines.push(`• ${rec}`));
lines.push('');
}
// Root cause analysis
if (report.analysis.rootCauseAnalysis.length > 0) {
lines.push('🎯 ROOT CAUSE ANALYSIS');
lines.push('───────────────────────────────────────────────────────────');
report.analysis.rootCauseAnalysis.forEach((root) => {
lines.push(`Cause: ${root.cause}`);
lines.push(`Confidence: ${(root.confidence * 100).toFixed(1)}%`);
lines.push(`Severity: ${root.severity.toUpperCase()}`);
lines.push(`Affected Errors: ${root.affectedErrors.length}`);
if (includeRemediation && root.remediation) {
lines.push(`Remediation: ${root.remediation}`);
}
lines.push('');
});
}
// Individual errors
if (report.errors.length > 0) {
lines.push('🔥 DETAILED ERRORS');
lines.push('───────────────────────────────────────────────────────────');
const filteredErrors = this.filterErrors(report.errors, options);
filteredErrors.forEach((error, index) => {
lines.push(
`[${index + 1}] Line ${error.line}: ${this.getSeverityIcon(error.pattern.severity)} ${error.pattern.severity.toUpperCase()}`
);
lines.push(` Category: ${error.pattern.category}`);
if (error.pattern.language) {
lines.push(` Language: ${error.pattern.language}`);
}
lines.push(` Message: ${error.match}`);
if (error.extractedInfo?.filePath) {
lines.push(
` File: ${error.extractedInfo.filePath}:${error.extractedInfo.lineNumber || '?'}`
);
}
if (includeRemediation && error.extractedInfo?.suggestion) {
lines.push(` 💡 Suggestion: ${error.extractedInfo.suggestion}`);
}
if (includeContext && error.context) {
if (
error.context.beforeLines &&
error.context.beforeLines.length > 0
) {
lines.push(' Context (before):');
error.context.beforeLines.slice(-2).forEach((line, i) => {
lines.push(` ${error.line - 2 + i}: ${line}`);
});
}
if (error.context.afterLines && error.context.afterLines.length > 0) {
lines.push(' Context (after):');
error.context.afterLines.slice(0, 2).forEach((line, i) => {
lines.push(` ${error.line + 1 + i}: ${line}`);
});
}
}
if (
error.extractedInfo?.stackTrace &&
error.extractedInfo.stackTrace.length > 0
) {
lines.push(' Stack Trace:');
error.extractedInfo.stackTrace.slice(0, 5).forEach((frame) => {
lines.push(` ${frame}`);
});
if (error.extractedInfo.stackTrace.length > 5) {
lines.push(
` ... (${error.extractedInfo.stackTrace.length - 5} more frames)`
);
}
}
lines.push('');
});
}
// Retryable errors
if (report.analysis.retryableErrors.length > 0) {
lines.push('🔄 RETRYABLE ERRORS');
lines.push('───────────────────────────────────────────────────────────');
report.analysis.retryableErrors.forEach((error, index) => {
lines.push(`[${index + 1}] Line ${error.line}: ${error.match}`);
lines.push(` Category: ${error.pattern.category}`);
if (error.extractedInfo?.suggestion) {
lines.push(` 💡 Suggestion: ${error.extractedInfo.suggestion}`);
}
lines.push('');
});
}
return lines.join('\n');
}
/**
* Format error report as JSON for API responses
*/
formatAsJson(report: ErrorReport, options: ReportingOptions = {}): string {
const filteredErrors = this.filterErrors(report.errors, options);
const jsonReport = {
meta: {
version: '2.0.0',
timestamp: new Date().toISOString(),
platform: process.platform,
options,
},
summary: report.summary,
analysis: {
...report.analysis,
errors: filteredErrors.map((error) => ({
line: error.line,
type: error.pattern.type,
category: error.pattern.category,
severity: error.pattern.severity,
language: error.pattern.language,
message: error.match,
description: error.pattern.description,
file: error.extractedInfo?.filePath,
lineNumber: error.extractedInfo?.lineNumber,
columnNumber: error.extractedInfo?.columnNumber,
errorCode: error.extractedInfo?.errorCode,
suggestion: error.extractedInfo?.suggestion,
retryable: error.pattern.retryable || false,
tags: error.pattern.tags || [],
context: options.includeContext ? error.context : undefined,
stackTrace: options.includeStackTraces
? error.extractedInfo?.stackTrace
: undefined,
})),
},
recommendations: report.recommendations,
};
return JSON.stringify(jsonReport, null, 2);
}
/**
* Format error report as Markdown for documentation
*/
formatAsMarkdown(
report: ErrorReport,
options: ReportingOptions = {}
): string {
const lines: string[] = [];
const { includeContext = false, includeRemediation = true } = options;
// Header
lines.push('# Error Analysis Report');
lines.push('');
lines.push(`**Generated:** ${new Date().toISOString()}`);
lines.push(`**Total Lines Analyzed:** ${report.summary.totalLines}`);
lines.push(`**Errors Found:** ${report.summary.errorsFound}`);
lines.push('');
// Summary table
lines.push('## Summary');
lines.push('');
lines.push('| Severity | Count |');
lines.push('|----------|-------|');
lines.push(`| Critical | ${report.summary.severityBreakdown.critical} |`);
lines.push(`| High | ${report.summary.severityBreakdown.high} |`);
lines.push(`| Medium | ${report.summary.severityBreakdown.medium} |`);
lines.push(`| Low | ${report.summary.severityBreakdown.low} |`);
lines.push('');
// Category breakdown
if (Object.keys(report.summary.categoryBreakdown).length > 0) {
lines.push('### Categories');
lines.push('');
lines.push('| Category | Count |');
lines.push('|----------|-------|');
Object.entries(report.summary.categoryBreakdown).forEach(
([category, count]) => {
lines.push(`| ${category} | ${count} |`);
}
);
lines.push('');
}
// Recommendations
if (report.recommendations.length > 0) {
lines.push('## Recommendations');
lines.push('');
report.recommendations.forEach((rec) => {
lines.push(`- ${rec.replace(/[🔥⚠️💡🌐🗄️🔨⚡🔒🔄🎯]/g, '').trim()}`);
});
lines.push('');
}
// Root cause analysis
if (report.analysis.rootCauseAnalysis.length > 0) {
lines.push('## Root Cause Analysis');
lines.push('');
report.analysis.rootCauseAnalysis.forEach((root, index) => {
lines.push(`### ${index + 1}. ${root.cause}`);
lines.push('');
lines.push(`**Confidence:** ${(root.confidence * 100).toFixed(1)}%`);
lines.push(`**Severity:** ${root.severity.toUpperCase()}`);
lines.push(`**Affected Errors:** ${root.affectedErrors.length}`);
if (includeRemediation && root.remediation) {
lines.push(`**Remediation:** ${root.remediation}`);
}
lines.push('');
});
}
// Detailed errors
if (report.errors.length > 0) {
lines.push('## Detailed Errors');
lines.push('');
const filteredErrors = this.filterErrors(report.errors, options);
filteredErrors.forEach((error, index) => {
lines.push(
`### ${index + 1}. ${error.pattern.description} (Line ${error.line})`
);
lines.push('');
lines.push(`**Severity:** ${error.pattern.severity.toUpperCase()}`);
lines.push(`**Category:** ${error.pattern.category}`);
if (error.pattern.language) {
lines.push(`**Language:** ${error.pattern.language}`);
}
lines.push(`**Message:** \`${error.match}\``);
if (error.extractedInfo?.filePath) {
lines.push(
`**File:** ${error.extractedInfo.filePath}:${error.extractedInfo.lineNumber || '?'}`
);
}
if (includeRemediation && error.extractedInfo?.suggestion) {
lines.push(`**Suggestion:** ${error.extractedInfo.suggestion}`);
}
if (includeContext && error.context) {
lines.push('');
lines.push('**Context:**');
lines.push('```');
if (error.context.beforeLines) {
error.context.beforeLines.forEach((line, i) => {
lines.push(
`${error.line - error.context.beforeLines!.length + i}: ${line}`
);
});
}
lines.push(`${error.line}: ${error.match}`);
if (error.context.afterLines) {
error.context.afterLines.forEach((line, i) => {
lines.push(`${error.line + 1 + i}: ${line}`);
});
}
lines.push('```');
}
lines.push('');
});
}
return lines.join('\n');
}
/**
* Send error report to monitoring systems
*/
async sendToMonitoring(
report: ErrorReport,
integrations: MonitoringIntegration[]
): Promise<void> {
const promises = integrations.map(async (integration) => {
try {
// Check if thresholds are met
if (integration.threshold) {
const meetsThreshold = this.checkThreshold(
report,
integration.threshold
);
if (!meetsThreshold) return;
}
switch (integration.type) {
case 'webhook':
await this.sendWebhook(report, integration.endpoint!);
break;
case 'file':
await this.writeToFile(report, integration.filePath!);
break;
case 'console':
console.log(this.formatForConsole(report));
break;
case 'custom':
if (integration.customHandler) {
await integration.customHandler(report);
}
break;
}
} catch (error) {
console.error(`Failed to send report to ${integration.type}:`, error);
}
});
await Promise.all(promises);
}
/**
* Send alert if thresholds are exceeded
*/
async sendAlert(report: ErrorReport): Promise<void> {
if (!this.alertConfig.enabled) return;
const alertKey = `${report.analysis.criticalErrors}-${report.analysis.highErrors}-${report.analysis.totalErrors}`;
const lastAlert = this.lastAlertTime.get(alertKey);
const now = new Date();
// Check cooldown
if (
lastAlert &&
now.getTime() - lastAlert.getTime() <
this.alertConfig.cooldownMinutes * 60 * 1000
) {
return;
}
// Check thresholds
const shouldAlert =
report.analysis.criticalErrors >= this.alertConfig.threshold.critical ||
report.analysis.highErrors >= this.alertConfig.threshold.high ||
report.analysis.totalErrors >= this.alertConfig.threshold.total;
if (!shouldAlert) return;
this.lastAlertTime.set(alertKey, now);
const alertMessage = this.formatAlert(report);
for (const channel of this.alertConfig.channels) {
try {
switch (channel) {
case 'console':
console.error('🚨 ERROR ALERT 🚨');
console.error(alertMessage);
break;
case 'webhook':
// Implementation depends on webhook configuration
break;
case 'email':
// Implementation depends on email configuration
break;
case 'slack':
// Implementation depends on Slack configuration
break;
}
} catch (error) {
console.error(`Failed to send alert to ${channel}:`, error);
}
}
}
/**
* Generate structured output for MCP responses
*/
generateMcpResponse(
report: ErrorReport,
options: ReportingOptions = {}
): any {
const filteredErrors = this.filterErrors(report.errors, options);
return {
type: 'error_analysis',
timestamp: new Date().toISOString(),
summary: {
total_lines: report.summary.totalLines,
errors_found: report.summary.errorsFound,
severity: {
critical: report.summary.severityBreakdown.critical,
high: report.summary.severityBreakdown.high,
medium: report.summary.severityBreakdown.medium,
low: report.summary.severityBreakdown.low,
},
categories: report.summary.categoryBreakdown,
},
analysis: {
root_causes: report.analysis.rootCauseAnalysis.map((root) => ({
cause: root.cause,
confidence: root.confidence,
severity: root.severity,
affected_count: root.affectedErrors.length,
remediation: root.remediation,
})),
correlated_errors: report.analysis.correlatedErrors.map((corr) => ({
primary_error: {
line: corr.primaryError.line,
message: corr.primaryError.match,
category: corr.primaryError.pattern.category,
},
related_count: corr.relatedErrors.length,
confidence: corr.confidence,
})),
retryable_count: report.analysis.retryableErrors.length,
},
errors: filteredErrors.map((error) => ({
line: error.line,
severity: error.pattern.severity,
category: error.pattern.category,
type: error.pattern.type,
message: error.match,
file: error.extractedInfo?.filePath,
suggestion: error.extractedInfo?.suggestion,
retryable: error.pattern.retryable || false,
})),
recommendations: report.recommendations.map((rec) =>
rec.replace(/[🔥⚠️💡🌐🗄️🔨⚡🔒🔄🎯]/g, '').trim()
),
actionable_insights: this.generateActionableInsights(report),
};
}
/**
* Filter errors based on reporting options
*/
private filterErrors(
errors: ParsedError[],
options: ReportingOptions
): ParsedError[] {
let filtered = errors;
if (options.severity) {
filtered = filtered.filter(
(error) => error.pattern.severity === options.severity
);
}
if (options.categories && options.categories.length > 0) {
filtered = filtered.filter((error) =>
options.categories!.includes(error.pattern.category)
);
}
if (options.languages && options.languages.length > 0) {
filtered = filtered.filter(
(error) =>
error.pattern.language &&
options.languages!.includes(error.pattern.language)
);
}
return filtered;
}
/**
* Check if report meets monitoring threshold
*/
private checkThreshold(report: ErrorReport, threshold: any): boolean {
if (
threshold.criticalErrors &&
report.analysis.criticalErrors < threshold.criticalErrors
) {
return false;
}
if (
threshold.totalErrors &&
report.analysis.totalErrors < threshold.totalErrors
) {
return false;
}
if (threshold.severityScore) {
const score = this.calculateSeverityScore(report);
if (score < threshold.severityScore) {
return false;
}
}
return true;
}
/**
* Calculate overall severity score
*/
private calculateSeverityScore(report: ErrorReport): number {
return (
report.analysis.criticalErrors * 4 +
report.analysis.highErrors * 3 +
report.analysis.mediumErrors * 2 +
report.analysis.lowErrors * 1
);
}
/**
* Send webhook notification
*/
private async sendWebhook(
report: ErrorReport,
endpoint: string
): Promise<void> {
const payload = this.generateMcpResponse(report);
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Webhook request failed: ${response.status}`);
}
} catch (error) {
console.error('Failed to send webhook:', error);
throw error;
}
}
/**
* Write report to file
*/
private async writeToFile(
report: ErrorReport,
filePath: string
): Promise<void> {
const fs = await import('fs/promises');
const content = this.formatAsJson(report);
await fs.writeFile(filePath, content, 'utf8');
}
/**
* Format alert message
*/
private formatAlert(report: ErrorReport): string {
const lines = [
`🚨 ERROR THRESHOLD EXCEEDED`,
`Time: ${new Date().toISOString()}`,
`Total Errors: ${report.analysis.totalErrors}`,
`Critical: ${report.analysis.criticalErrors}`,
`High: ${report.analysis.highErrors}`,
`Categories: ${Object.keys(report.summary.categoryBreakdown).join(', ')}`,
];
if (report.analysis.rootCauseAnalysis.length > 0) {
lines.push('Root Causes:');
report.analysis.rootCauseAnalysis.forEach((root) => {
lines.push(
`- ${root.cause} (confidence: ${(root.confidence * 100).toFixed(0)}%)`
);
});
}
return lines.join('\n');
}
/**
* Generate actionable insights
*/
private generateActionableInsights(report: ErrorReport): string[] {
const insights: string[] = [];
// Priority insights based on severity
if (report.analysis.criticalErrors > 0) {
insights.push(
`IMMEDIATE ACTION: ${report.analysis.criticalErrors} critical errors need immediate attention`
);
}
// Category-specific insights
const categoryCount = Object.keys(report.summary.categoryBreakdown).length;
if (categoryCount > 3) {
insights.push(
`SYSTEMIC ISSUES: Errors span ${categoryCount} categories, indicating broader system problems`
);
}
// Root cause insights
const highConfidenceRootCauses = report.analysis.rootCauseAnalysis.filter(
(r) => r.confidence > 0.8
);
if (highConfidenceRootCauses.length > 0) {
insights.push(
`ROOT CAUSE IDENTIFIED: ${highConfidenceRootCauses[0].cause} with ${(highConfidenceRootCauses[0].confidence * 100).toFixed(0)}% confidence`
);
}
// Retry insights
if (report.analysis.retryableErrors.length > 0) {
insights.push(
`RETRY OPPORTUNITY: ${report.analysis.retryableErrors.length} errors may be resolved by retrying`
);
}
return insights;
}
/**
* Get category icon for display
*/
private getCategoryIcon(category: string): string {
const icons: Record<string, string> = {
runtime: '🔥',
compilation: '🔨',
network: '🌐',
database: '🗄️',
performance: '⚡',
security: '🔒',
ssh: '🔑',
'build-tool': '🛠️',
configuration: '⚙️',
};
return icons[category] || '❓';
}
/**
* Get severity icon for display
*/
private getSeverityIcon(severity: string): string {
const icons: Record<string, string> = {
critical: '🔴',
high: '🟠',
medium: '🟡',
low: '🟢',
};
return icons[severity] || '⚪';
}
}