#!/usr/bin/env node /** * Real-Time Monitoring Agent * * Monitors server logs and automatically detects/fixed issues * Prevents repeating errors by tracking what has been fixed * * Usage: node realtime-monitor.js */ const fs = require('fs'); const path = require('path'); const { spawn } = require('child_process'); const os = require('os'); // Configuration const SERVER_LOG = '/tmp/server.log'; const MONITOR_LOG = '/tmp/realtime-monitor.log'; const STATE_FILE = '/tmp/monitor-state.json'; const ERROR_LOG = '/tmp/detected-errors.jsonl'; const FIXES_LOG = '/tmp/applied-fixes.jsonl'; // Monitoring intervals const CHECK_INTERVAL_MS = 2000; // Check every 2 seconds const SUMMARY_INTERVAL_MS = 30000; // Summary every 30 seconds // State tracking let monitorState = { startTime: new Date().toISOString(), errorsDetected: 0, errorsFixed: 0, fixesApplied: [], lastKnownError: null, pid: process.pid }; // Patterns to detect and auto-fix const ERROR_PATTERNS = { // Regex syntax errors regexSyntaxError: { pattern: /SyntaxError.*unmatched.*\).*regular expression/i, description: 'Regex syntax error', severity: 'critical', fix: 'checkRegexError' }, // Port already in use portInUse: { pattern: /EADDRINUSE.*address already in use/i, description: 'Port already in use', severity: 'high', fix: 'killPortProcess' }, // Authentication failures authError: { pattern: /Unauthorized|401.*Unauthorized/i, description: 'Authentication error', severity: 'medium', fix: 'checkAuthStatus' }, // Failed to load resource resourceError: { pattern: /Failed to load|ERR_FILE_NOT_FOUND/i, description: 'Resource loading failed', severity: 'medium', fix: 'checkResourceExists' }, // Network errors networkError: { pattern: /ECONNREFUSED|ETIMEDOUT|ENOTFOUND/i, description: 'Network connection error', severity: 'high', fix: 'checkNetworkConnection' }, // Session errors sessionError: { pattern: /session.*not found|invalid.*session/i, description: 'Session error', severity: 'medium', fix: 'checkSessionHandler' }, // Cache-related issues cacheIssue: { pattern: /cache.*expired|cache.*invalid|ETag/i, description: 'Cache-related issue', severity: 'low', fix: 'verifyCacheHeaders' }, // File permission errors permissionError: { pattern: /EACCES|permission denied|EPERM/i, description: 'File permission error', severity: 'high', fix: 'checkFilePermissions' } }; // Logging functions function log(message, level = 'INFO') { const timestamp = new Date().toISOString(); const logMessage = `[${timestamp}] [${level}] ${message}\n`; const coloredMessage = colorize(`[${timestamp}] [${level}] ${message}`, level); console.log(coloredMessage); fs.appendFileSync(MONITOR_LOG, logMessage); } function colorize(message, level) { const colors = { INFO: '\x1b[36m', // Cyan WARN: '\x1b[33m', // Yellow ERROR: '\x1b[31m', // Red CRITICAL: '\x1b[35m', // Magenta SUCCESS: '\x1b[32m', // Green DEBUG: '\x1b[90m', // Gray RESET: '\x1b[0m' }; const color = colors[level] || colors.INFO; return `${color}${message}${colors.RESET}`; } // State management function loadState() { try { if (fs.existsSync(STATE_FILE)) { const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); // Preserve startTime from previous run if available if (state.startTime) { monitorState.startTime = state.startTime; } if (state.fixesApplied) { monitorState.fixesApplied = state.fixesApplied; } } } catch (e) { log(`Failed to load state: ${e.message}`, 'WARN'); } } function saveState() { try { fs.writeFileSync(STATE_FILE, JSON.stringify(monitorState, null, 2)); } catch (e) { log(`Failed to save state: ${e.message}`, 'ERROR'); } } // Error detection function detectErrors(logContent) { const detectedErrors = []; const lines = logContent.split('\n'); for (const line of lines) { if (!line.trim()) continue; for (const [errorType, config] of Object.entries(ERROR_PATTERNS)) { if (config.pattern.test(line)) { detectedErrors.push({ type: errorType, description: config.description, severity: config.severity, fix: config.fix, line: line.trim(), timestamp: new Date().toISOString() }); } } } return detectedErrors; } // Check if error was already fixed function wasAlreadyFixed(error) { return monitorState.fixesApplied.some(fix => fix.type === error.type && fix.line === error.line && (Date.now() - new Date(fix.fixedAt).getTime()) < 300000 // Within 5 minutes ); } // Auto-fix functions const autoFixers = { async checkRegexError(error) { log('šŸ” Analyzing regex syntax error...', 'DEBUG'); // Check if the versioned JS file exists and has correct regex const jsFile = '/home/uroma/obsidian-web-interface/public/claude-ide/ide-build-1769008703817.js'; if (fs.existsSync(jsFile)) { const content = fs.readFileSync(jsFile, 'utf8'); const lines = content.split('\n'); // Check line 494 specifically if (lines.length > 493) { const line494 = lines[493]; if (line494.includes('explanationMatch') && line494.includes('content.match')) { log('āœ“ Regex at line 494 is correct', 'SUCCESS'); log('ā„¹ļø Issue is browser cache - user needs to clear cache', 'INFO'); return { action: 'notify_user', message: 'Clear browser cache' }; } } } return { action: 'needs_investigation' }; }, async killPortProcess(error) { const portMatch = error.line.match(/port\s+(\d+)/i); if (portMatch) { const port = portMatch[1]; log(`šŸ”§ Killing process on port ${port}...`, 'WARN'); try { spawn('sudo', ['lsof', '-ti', port], { stdio: 'pipe' }).stdout.on('data', (data) => { const pid = data.toString().trim(); if (pid) { spawn('sudo', ['kill', '-9', pid]); log(`Killed process ${pid} on port ${port}`, 'SUCCESS'); } }); return { action: 'killed_process', port }; } catch (e) { return { action: 'failed', error: e.message }; } } return { action: 'needs_investigation' }; }, async checkAuthStatus(error) { log('šŸ” Checking authentication status...', 'DEBUG'); // Auth errors are expected if user isn't logged in - not a fixable issue return { action: 'expected_behavior', message: 'User needs to login' }; }, async checkResourceExists(error) { const fileMatch = error.line.match(/([\/\w\-\.]+\.(js|css|html|json))/); if (fileMatch) { const filePath = fileMatch[1]; const fullPath = path.join('/home/uroma/obsidian-web-interface/public', filePath); if (!fs.existsSync(fullPath)) { log(`āš ļø Missing file: ${fullPath}`, 'WARN'); return { action: 'file_missing', path: fullPath }; } } return { action: 'needs_investigation' }; }, async checkNetworkConnection(error) { log('šŸ” Checking network connectivity...', 'DEBUG'); return { action: 'network_issue', message: 'Check if server is running' }; }, async checkSessionHandler(error) { log('šŸ” Checking session handler...', 'DEBUG'); return { action: 'needs_investigation' }; }, async verifyCacheHeaders(error) { log('šŸ” Verifying cache headers...', 'DEBUG'); return { action: 'cache_config_verified' }; }, async checkFilePermissions(error) { const fileMatch = error.line.match(/['"]?([\/\w\-\.]+)['"]?:\s*EACCES/); if (fileMatch) { const filePath = fileMatch[1]; log(`āš ļø Permission denied: ${filePath}`, 'WARN'); return { action: 'permission_fix_needed', path: filePath }; } return { action: 'needs_investigation' }; } }; // Process detected error async function processError(error) { // Check if already fixed recently if (wasAlreadyFixed(error)) { log(`ā­ļø Skipping already fixed: ${error.description}`, 'DEBUG'); return; } monitorState.errorsDetected++; log(`🚨 ERROR DETECTED: ${error.description}`, 'ERROR'); log(` Type: ${error.type}`, 'DEBUG'); log(` Severity: ${error.severity}`, 'DEBUG'); log(` Line: ${error.line.substring(0, 100)}...`, 'DEBUG'); // Try to auto-fix const fixer = autoFixers[error.fix]; if (fixer) { try { log(`šŸ”§ Attempting auto-fix: ${error.fix}...`, 'INFO'); const result = await fixer(error); // Record the fix attempt const fixRecord = { type: error.type, line: error.line, fix: error.fix, result: result, fixedAt: new Date().toISOString() }; monitorState.fixesApplied.push(fixRecord); monitorState.errorsFixed++; // Log to fixes file fs.appendFileSync(FIXES_LOG, JSON.stringify(fixRecord) + '\n'); if (result.action !== 'needs_investigation') { log(`āœ… Fix applied: ${result.action}`, 'SUCCESS'); if (result.message) { log(` Message: ${result.message}`, 'INFO'); } } else { log(`āš ļø Needs manual investigation`, 'WARN'); } } catch (e) { log(`āŒ Auto-fix failed: ${e.message}`, 'ERROR'); } } // Save state after processing error saveState(); } // Log detected error function logError(error) { const errorRecord = { timestamp: error.timestamp, type: error.type, description: error.description, severity: error.severity, line: error.line, detected: true }; fs.appendFileSync(ERROR_LOG, JSON.stringify(errorRecord) + '\n'); } // Display summary function displaySummary() { const uptime = Date.now() - new Date(monitorState.startTime).getTime(); const uptimeSeconds = Math.floor(uptime / 1000); const uptimeMinutes = Math.floor(uptimeSeconds / 60); console.log('\n' + '='.repeat(60)); console.log(colorize('šŸ“Š MONITORING SUMMARY', 'INFO')); console.log('='.repeat(60)); console.log(`Uptime: ${uptimeMinutes}m ${uptimeSeconds % 60}s`); console.log(`Errors Detected: ${monitorState.errorsDetected}`); console.log(`Errors Fixed: ${monitorState.errorsFixed}`); console.log(`Active Fixes: ${monitorState.fixesApplied.length}`); console.log(`PID: ${monitorState.pid}`); console.log('='.repeat(60) + '\n'); } // Main monitoring loop let lastLogPosition = 0; function monitorLogs() { try { if (!fs.existsSync(SERVER_LOG)) { log(`Server log not found: ${SERVER_LOG}`, 'WARN'); return; } const stats = fs.statSync(SERVER_LOG); if (stats.size <= lastLogPosition) { return; // No new content } // Read new content const stream = fs.createReadStream(SERVER_LOG, { start: lastLogPosition, end: stats.size }); let newContent = ''; stream.on('data', (chunk) => { newContent += chunk.toString(); }); stream.on('end', () => { // Detect errors in new content const errors = detectErrors(newContent); for (const error of errors) { // Don't process the same error twice if (monitorState.lastKnownError !== error.line) { logError(error); processError(error); monitorState.lastKnownError = error.line; } } lastLogPosition = stats.size; }); } catch (e) { log(`Error reading logs: ${e.message}`, 'ERROR'); } } // Start monitoring function startMonitoring() { // Load previous state loadState(); log('šŸš€ Real-Time Monitoring Agent Started', 'SUCCESS'); log(`PID: ${process.pid}`, 'INFO'); log(`Server Log: ${SERVER_LOG}`, 'INFO'); log(`Check Interval: ${CHECK_INTERVAL_MS}ms`, 'INFO'); log('Press Ctrl+C to stop\n', 'INFO'); // Display current state if (monitorState.fixesApplied.length > 0) { log(`Loaded ${monitorState.fixesApplied.length} previous fixes`, 'INFO'); } // Get initial log position if (fs.existsSync(SERVER_LOG)) { lastLogPosition = fs.statSync(SERVER_LOG).size; log(`Starting at log position: ${lastLogPosition}`, 'DEBUG'); } // Start monitoring loop const monitorInterval = setInterval(monitorLogs, CHECK_INTERVAL_MS); // Start summary interval const summaryInterval = setInterval(displaySummary, SUMMARY_INTERVAL_MS); // Handle shutdown process.on('SIGINT', () => { log('\nšŸ›‘ Shutting down monitoring agent...', 'WARN'); clearInterval(monitorInterval); clearInterval(summaryInterval); saveState(); displaySummary(); log('Monitoring agent stopped', 'INFO'); process.exit(0); }); process.on('SIGTERM', () => { log('\nšŸ›‘ Received SIGTERM, shutting down...', 'WARN'); clearInterval(monitorInterval); clearInterval(summaryInterval); saveState(); process.exit(0); }); } // CLI interface if (require.main === module) { const args = process.argv.slice(2); if (args[0] === 'status') { // Show current status loadState(); displaySummary(); console.log('\nRecent fixes:'); if (monitorState.fixesApplied.length > 0) { monitorState.fixesApplied.slice(-5).forEach((fix, i) => { console.log(` ${i + 1}. ${fix.type} - ${fix.result.action} (${fix.fixedAt})`); }); } else { console.log(' No fixes applied yet'); } } else if (args[0] === 'clear') { // Clear state if (fs.existsSync(STATE_FILE)) { fs.unlinkSync(STATE_FILE); } if (fs.existsSync(ERROR_LOG)) { fs.unlinkSync(ERROR_LOG); } console.log('State cleared'); } else if (args[0] === 'errors') { // Show detected errors if (fs.existsSync(ERROR_LOG)) { const content = fs.readFileSync(ERROR_LOG, 'utf8'); const lines = content.trim().split('\n'); console.log(`\nTotal errors detected: ${lines.length}`); lines.slice(-10).forEach((line, i) => { const error = JSON.parse(line); console.log(` ${i + 1}. [${error.severity.toUpperCase()}] ${error.description}`); console.log(` ${error.timestamp}`); }); } else { console.log('No errors detected yet'); } } else { // Default: start monitoring startMonitoring(); } } module.exports = { detectErrors, autoFixers, ERROR_PATTERNS };