UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

395 lines (394 loc) 11.3 kB
/** * Structured Logger for Enhanced Error Reporting * * Provides comprehensive logging with structured data, error tracking, * and integration with the health monitoring system. */ export const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, FATAL: 4 }; export class StructuredLogger { constructor(options) { this.logs = []; this.maxLogs = 10000; this.currentLevel = 'INFO'; this.enableConsole = true; if (options?.maxLogs) this.maxLogs = options.maxLogs; if (options?.level) this.currentLevel = options.level; if (options?.enableConsole !== undefined) this.enableConsole = options.enableConsole; } /** * Set logging level */ setLevel(level) { this.currentLevel = level; } /** * Enable or disable console output */ setConsoleOutput(enabled) { this.enableConsole = enabled; } /** * Log debug message */ debug(message, context = {}) { this.log('DEBUG', message, context); } /** * Log info message */ info(message, context = {}) { this.log('INFO', message, context); } /** * Log warning message */ warn(message, context = {}) { this.log('WARN', message, context); } /** * Log error message */ error(message, context = {}, error) { const errorInfo = error ? { name: error.name, message: error.message, stack: error.stack } : undefined; this.log('ERROR', message, context, errorInfo); } /** * Log fatal error message */ fatal(message, context = {}, error) { const errorInfo = error ? { name: error.name, message: error.message, stack: error.stack } : undefined; this.log('FATAL', message, context, errorInfo); } /** * Log consensus stage start */ stageStart(stage, context = {}) { this.info(`Starting ${stage} stage`, { ...context, stage, event: 'stage_start' }); } /** * Log consensus stage completion */ stageComplete(stage, duration, context = {}) { this.info(`Completed ${stage} stage`, { ...context, stage, duration, event: 'stage_complete' }); } /** * Log consensus stage failure */ stageError(stage, error, context = {}) { this.error(`Failed ${stage} stage`, { ...context, stage, event: 'stage_error' }, error); } /** * Log API request start */ apiRequestStart(provider, model, context = {}) { this.debug(`API request started`, { ...context, provider, model, event: 'api_request_start' }); } /** * Log API request completion */ apiRequestComplete(provider, model, duration, context = {}) { this.debug(`API request completed`, { ...context, provider, model, duration, event: 'api_request_complete' }); } /** * Log API request error */ apiRequestError(provider, model, error, context = {}) { this.error(`API request failed`, { ...context, provider, model, event: 'api_request_error' }, error); } /** * Log circuit breaker event */ circuitBreakerEvent(provider, model, state, context = {}) { this.warn(`Circuit breaker ${state}`, { ...context, provider, model, circuitState: state, event: 'circuit_breaker' }); } /** * Log fallback event */ fallbackEvent(fromProvider, fromModel, toProvider, toModel, context = {}) { this.warn(`Fallback triggered`, { ...context, fromProvider, fromModel, toProvider, toModel, event: 'fallback' }); } /** * Log health check results */ healthCheck(service, status, responseTime, context = {}) { this.info(`Health check: ${service}`, { ...context, service, healthStatus: status, responseTime, event: 'health_check' }); } /** * Core logging method */ log(level, message, context, error) { // Check if we should log this level if (LOG_LEVELS[level] < LOG_LEVELS[this.currentLevel]) { return; } const logEntry = { timestamp: new Date().toISOString(), level, message, context, error }; // Store in memory this.logs.push(logEntry); // Trim logs if needed if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(-this.maxLogs); } // Console output if (this.enableConsole) { this.outputToConsole(logEntry); } } /** * Output log entry to console with formatting */ outputToConsole(entry) { const timestamp = entry.timestamp.split('T')[1].split('.')[0]; const level = entry.level.padEnd(5); let color = ''; let reset = '\x1b[0m'; switch (entry.level) { case 'DEBUG': color = '\x1b[36m'; break; // Cyan case 'INFO': color = '\x1b[32m'; break; // Green case 'WARN': color = '\x1b[33m'; break; // Yellow case 'ERROR': color = '\x1b[31m'; break; // Red case 'FATAL': color = '\x1b[35m'; break; // Magenta } let output = `${color}[${timestamp}] ${level}${reset} ${entry.message}`; // Add context if significant const contextKeys = Object.keys(entry.context); if (contextKeys.length > 0) { const significantContext = contextKeys .filter(key => ['provider', 'model', 'stage', 'duration', 'healthStatus'].includes(key)) .map(key => `${key}=${entry.context[key]}`) .join(' '); if (significantContext) { output += ` [${significantContext}]`; } } console.log(output); // Log error details if present if (entry.error && entry.level === 'ERROR') { console.log(`${color} Error: ${entry.error.message}${reset}`); } } /** * Get recent logs */ getRecentLogs(count = 100) { return this.logs.slice(-count); } /** * Get logs by level */ getLogsByLevel(level, hours = 1) { const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000); return this.logs.filter(log => log.level === level && new Date(log.timestamp) >= cutoff); } /** * Get logs by context */ getLogsByContext(contextFilter, hours = 1) { const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000); return this.logs.filter(log => { if (new Date(log.timestamp) < cutoff) return false; return Object.entries(contextFilter).every(([key, value]) => log.context[key] === value); }); } /** * Get error statistics */ getErrorStats(hours = 1) { const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000); const errorLogs = this.logs.filter(log => (log.level === 'ERROR' || log.level === 'FATAL') && new Date(log.timestamp) >= cutoff); const errorsByType = {}; const errorsByProvider = {}; const errorsByStage = {}; errorLogs.forEach(log => { // Count by error type if (log.context.errorType) { errorsByType[log.context.errorType] = (errorsByType[log.context.errorType] || 0) + 1; } // Count by provider if (log.context.provider) { errorsByProvider[log.context.provider] = (errorsByProvider[log.context.provider] || 0) + 1; } // Count by stage if (log.context.stage) { errorsByStage[log.context.stage] = (errorsByStage[log.context.stage] || 0) + 1; } }); return { totalErrors: errorLogs.length, errorsByType, errorsByProvider, errorsByStage }; } /** * Clear logs */ clearLogs() { this.logs = []; } /** * Export logs as JSON */ exportLogs() { return JSON.stringify(this.logs, null, 2); } } /** * Determine appropriate log level based on environment */ function getEnvironmentLogLevel() { // Check for explicit debug environment variables if (process.env.HIVE_DEBUG === 'true' || process.env.DEBUG === 'true' || process.env.HIVE_VERBOSE === 'true') { return 'DEBUG'; } // Check for numeric log level const logLevel = process.env.HIVE_LOG_LEVEL || process.env.LOG_LEVEL; if (logLevel) { const numLevel = parseInt(logLevel); switch (numLevel) { case 0: return 'DEBUG'; case 1: return 'INFO'; case 2: return 'WARN'; case 3: return 'ERROR'; case 4: return 'FATAL'; } } // Development vs Production detection if (process.env.NODE_ENV === 'development') { return 'DEBUG'; } // Default for production users - hide debug noise return 'WARN'; } /** * Enhanced StructuredLogger with debug patterns */ class EnhancedStructuredLogger extends StructuredLogger { /** * Cost calculation debug logging (💰 prefix) */ costDebug(message, context = {}) { this.debug(`💰 ${message}`, { ...context, category: 'cost' }); } /** * Consensus pipeline debug logging (📚 prefix) */ consensusDebug(message, context = {}) { this.debug(`📚 ${message}`, { ...context, category: 'consensus' }); } /** * Pipeline enhancement debug logging (🔄 prefix) */ pipelineDebug(message, context = {}) { this.debug(`🔄 ${message}`, { ...context, category: 'pipeline' }); } /** * Toggle debug mode on/off */ enableDebugMode(enabled = true) { if (enabled) { this.setLevel('DEBUG'); console.log('🐛 Debug mode enabled - detailed logs will be shown'); } else { this.setLevel('WARN'); console.log('📱 Production mode - debug logs hidden'); } } /** * Check if debug mode is active */ isDebugMode() { return this.currentLevel === 'DEBUG'; } } // Global structured logger instance with environment-based configuration export const structuredLogger = new EnhancedStructuredLogger({ level: getEnvironmentLogLevel(), enableConsole: true });