← back to Designer Wallcoverings

DW-Agents/dw-agents/log-monitor-live.ts

575 lines

/**
 * DW-Agents: Live Log Monitor Agent
 * Real-time system monitoring with live data
 * Port: 7239
 */

import express, { Request, Response } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import cors from 'cors';

const execAsync = promisify(exec);
const app = express();
const PORT = 7239;

// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('public'));

// Live data cache
let liveData = {
  lastUpdate: Date.now(),
  processes: [] as any[],
  logs: [] as any[],
  systemMetrics: {} as any,
  errors: [] as any[],
  alerts: [] as any[]
};

// Error patterns
const ERROR_PATTERNS = [
  { pattern: /error|exception|fatal|crash|failed/i, severity: 'high', category: 'error' },
  { pattern: /ECONNREFUSED|ETIMEDOUT|ENOTFOUND/i, severity: 'critical', category: 'network' },
  { pattern: /Cannot find module|MODULE_NOT_FOUND/i, severity: 'critical', category: 'dependency' },
  { pattern: /Port \d+ is already in use|EADDRINUSE/i, severity: 'critical', category: 'port_conflict' },
  { pattern: /Memory usage|heap out of memory/i, severity: 'critical', category: 'memory' },
  { pattern: /5\d\d/i, severity: 'high', category: 'http_error' }
];

// Collect real PM2 process data
async function collectProcessData(): Promise<any[]> {
  try {
    const { stdout } = await execAsync('pm2 jlist');
    const processes = JSON.parse(stdout || '[]');
    
    return processes.map((proc: any) => ({
      name: proc.name,
      pid: proc.pid,
      status: proc.pm2_env?.status,
      cpu: proc.monit?.cpu || 0,
      memory: proc.monit?.memory || 0,
      restarts: proc.pm2_env?.restart_time || 0,
      uptime: proc.pm2_env?.pm_uptime ? Date.now() - proc.pm2_env.pm_uptime : 0,
      port: extractPort(proc.pm2_env?.pm_exec_path || ''),
      errors: proc.pm2_env?.axm_monitor?.['Loop delay'] || 0
    }));
  } catch (error) {
    console.error('Error collecting process data:', error);
    return [];
  }
}

// Extract port from file path or environment
function extractPort(filePath: string): number | null {
  const portMatches = filePath.match(/(\d{4,5})/g);
  return portMatches ? parseInt(portMatches[portMatches.length - 1]) : null;
}

// Collect real PM2 logs
async function collectLogData(): Promise<any[]> {
  try {
    const { stdout } = await execAsync('pm2 logs --lines 50 --json --nostream');
    const logLines = stdout.split('\n').filter(line => line.trim()).slice(0, 50);
    
    return logLines.map(line => {
      try {
        const logEntry = JSON.parse(line);
        const error = analyzeLogForErrors(logEntry.message);
        
        return {
          timestamp: new Date(logEntry.timestamp),
          process: logEntry.app_name,
          type: logEntry.type,
          message: logEntry.message.substring(0, 200),
          fullMessage: logEntry.message,
          error: error,
          severity: error ? error.severity : 'info'
        };
      } catch {
        return null;
      }
    }).filter(Boolean);
  } catch (error) {
    console.error('Error collecting log data:', error);
    return [];
  }
}

// Analyze log message for errors
function analyzeLogForErrors(message: string): any | null {
  for (const pattern of ERROR_PATTERNS) {
    if (pattern.pattern.test(message)) {
      return {
        category: pattern.category,
        severity: pattern.severity,
        pattern: pattern.pattern.source
      };
    }
  }
  return null;
}

// Collect system metrics
async function collectSystemMetrics(): Promise<any> {
  try {
    const [cpuInfo, memInfo, diskInfo] = await Promise.all([
      execAsync("top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - $1}'"),
      execAsync("free -m | awk 'NR==2{printf \"%s/%sMB (%.2f%%)\", $3,$2,$3*100/$2}'"),
      execAsync("df -h / | awk 'NR==2{print $3\"/\"$2\" (\"$5\")\"}' ")
    ]);

    return {
      cpu: parseFloat(cpuInfo.stdout.trim()) || 0,
      memory: memInfo.stdout.trim(),
      disk: diskInfo.stdout.trim(),
      timestamp: new Date()
    };
  } catch (error) {
    return {
      cpu: 0,
      memory: 'N/A',
      disk: 'N/A',
      timestamp: new Date()
    };
  }
}

// Update live data
async function updateLiveData() {
  try {
    const [processes, logs, systemMetrics] = await Promise.all([
      collectProcessData(),
      collectLogData(),
      collectSystemMetrics()
    ]);

    // Find new errors
    const newErrors = logs.filter(log => log.error && log.error.severity === 'critical' || log.error?.severity === 'high');
    
    // Create alerts for critical issues
    const newAlerts = newErrors.map(error => ({
      id: Date.now() + Math.random(),
      timestamp: new Date(),
      process: error.process,
      severity: error.error.severity,
      category: error.error.category,
      message: error.message,
      resolved: false
    }));

    liveData = {
      lastUpdate: Date.now(),
      processes: processes,
      logs: logs,
      systemMetrics: systemMetrics,
      errors: newErrors,
      alerts: [...liveData.alerts.filter(a => !a.resolved), ...newAlerts].slice(-20)
    };

  } catch (error) {
    console.error('Error updating live data:', error);
  }
}

// Health check
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    service: 'Live Log Monitor',
    port: PORT,
    uptime: process.uptime(),
    lastUpdate: new Date(liveData.lastUpdate)
  });
});

// API endpoints
app.get('/api/live-data', (req, res) => {
  res.json(liveData);
});

app.get('/api/processes', (req, res) => {
  res.json(liveData.processes);
});

app.get('/api/logs', (req, res) => {
  res.json(liveData.logs);
});

app.get('/api/errors', (req, res) => {
  res.json(liveData.errors);
});

app.get('/api/alerts', (req, res) => {
  res.json(liveData.alerts);
});

app.post('/api/alerts/:id/resolve', (req, res) => {
  const alertId = parseFloat(req.params.id);
  const alert = liveData.alerts.find(a => a.id === alertId);
  if (alert) {
    alert.resolved = true;
    alert.resolvedAt = new Date();
  }
  res.json({ success: true });
});

// Main dashboard
app.get('/', (req, res) => {
  const html = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>🔥 Live Log Monitor - Real Data</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
      color: white;
      min-height: 100vh;
      padding: 20px;
    }

    .container {
      max-width: 1400px;
      margin: 0 auto;
    }

    .header {
      text-align: center;
      margin-bottom: 30px;
      padding: 20px;
      background: rgba(255,255,255,0.1);
      border-radius: 15px;
      backdrop-filter: blur(10px);
    }

    .header h1 {
      font-size: 2.5em;
      margin-bottom: 10px;
      background: linear-gradient(45deg, #ef4444, #f97316, #eab308);
      background-clip: text;
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    .live-indicator {
      display: inline-flex;
      align-items: center;
      gap: 10px;
      background: rgba(34, 197, 94, 0.2);
      padding: 8px 16px;
      border-radius: 25px;
      border: 1px solid #22c55e;
    }

    .pulse {
      width: 12px;
      height: 12px;
      background: #22c55e;
      border-radius: 50%;
      animation: pulse 2s infinite;
    }

    @keyframes pulse {
      0% { transform: scale(1); opacity: 1; }
      50% { transform: scale(1.2); opacity: 0.7; }
      100% { transform: scale(1); opacity: 1; }
    }

    .metrics-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 20px;
      margin-bottom: 30px;
    }

    .metric-card {
      background: rgba(255,255,255,0.1);
      border-radius: 15px;
      padding: 20px;
      backdrop-filter: blur(10px);
      border: 1px solid rgba(255,255,255,0.2);
    }

    .metric-header {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 15px;
      font-size: 1.2em;
      font-weight: 600;
    }

    .data-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
    }

    .data-section {
      background: rgba(255,255,255,0.05);
      border-radius: 15px;
      padding: 20px;
    }

    .section-title {
      font-size: 1.3em;
      font-weight: 600;
      margin-bottom: 15px;
      color: #fbbf24;
    }

    .process-item, .log-item, .alert-item {
      background: rgba(255,255,255,0.1);
      padding: 12px;
      border-radius: 8px;
      margin-bottom: 10px;
      border-left: 4px solid #3b82f6;
    }

    .process-item.online { border-left-color: #22c55e; }
    .process-item.stopped { border-left-color: #ef4444; }
    .process-item.errored { border-left-color: #f59e0b; }

    .log-item.error { border-left-color: #ef4444; }
    .log-item.warn { border-left-color: #f59e0b; }
    
    .alert-item.critical { border-left-color: #dc2626; background: rgba(239, 68, 68, 0.1); }
    .alert-item.high { border-left-color: #f59e0b; background: rgba(245, 158, 11, 0.1); }

    .status-badge {
      display: inline-block;
      padding: 4px 8px;
      border-radius: 12px;
      font-size: 0.8em;
      font-weight: 600;
      text-transform: uppercase;
    }

    .status-online { background: #22c55e; color: white; }
    .status-stopped { background: #ef4444; color: white; }
    .status-errored { background: #f59e0b; color: white; }

    .metric-value {
      font-size: 1.8em;
      font-weight: 700;
      color: #22c55e;
    }

    .timestamp {
      font-size: 0.8em;
      color: #94a3b8;
    }

    .resolve-btn {
      background: #059669;
      color: white;
      border: none;
      padding: 4px 8px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 0.8em;
    }

    .resolve-btn:hover {
      background: #047857;
    }

    .error-count {
      background: #dc2626;
      color: white;
      padding: 2px 8px;
      border-radius: 12px;
      font-size: 0.8em;
      margin-left: 8px;
    }

    @media (max-width: 768px) {
      .data-grid { grid-template-columns: 1fr; }
      .metrics-grid { grid-template-columns: 1fr; }
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>🔥 Live Log Monitor</h1>
      <div class="live-indicator">
        <div class="pulse"></div>
        <span>LIVE DATA - Real-time System Monitoring</span>
      </div>
      <div class="timestamp" id="lastUpdate">Last Update: Loading...</div>
    </div>

    <div class="metrics-grid">
      <div class="metric-card">
        <div class="metric-header">🖥️ System CPU</div>
        <div class="metric-value" id="cpuUsage">---%</div>
      </div>
      <div class="metric-card">
        <div class="metric-header">💾 Memory Usage</div>
        <div class="metric-value" id="memoryUsage">--- MB</div>
      </div>
      <div class="metric-card">
        <div class="metric-header">💿 Disk Usage</div>
        <div class="metric-value" id="diskUsage">---</div>
      </div>
      <div class="metric-card">
        <div class="metric-header">⚡ Active Processes</div>
        <div class="metric-value" id="processCount">---</div>
      </div>
    </div>

    <div class="data-grid">
      <div class="data-section">
        <div class="section-title">📊 Live Processes</div>
        <div id="processList">Loading processes...</div>
      </div>

      <div class="data-section">
        <div class="section-title">📝 Recent Logs</div>
        <div id="logsList">Loading logs...</div>
      </div>
    </div>

    <div class="data-section">
      <div class="section-title">🚨 Active Alerts <span class="error-count" id="alertCount">0</span></div>
      <div id="alertsList">No active alerts</div>
    </div>
  </div>

  <script>
    let updateInterval;

    async function fetchLiveData() {
      try {
        const response = await fetch('/api/live-data');
        const data = await response.json();
        
        updateMetrics(data);
        updateProcesses(data.processes);
        updateLogs(data.logs);
        updateAlerts(data.alerts);
        
        document.getElementById('lastUpdate').textContent = 
          'Last Update: ' + new Date(data.lastUpdate).toLocaleTimeString();
          
      } catch (error) {
        console.error('Error fetching live data:', error);
      }
    }

    function updateMetrics(data) {
      document.getElementById('cpuUsage').textContent = data.systemMetrics.cpu.toFixed(1) + '%';
      document.getElementById('memoryUsage').textContent = data.systemMetrics.memory;
      document.getElementById('diskUsage').textContent = data.systemMetrics.disk;
      document.getElementById('processCount').textContent = data.processes.filter(p => p.status === 'online').length;
    }

    function updateProcesses(processes) {
      const container = document.getElementById('processList');
      container.innerHTML = processes.map(proc => \`
        <div class="process-item \${proc.status}">
          <div style="display: flex; justify-content: space-between; align-items: center;">
            <div>
              <strong>\${proc.name}</strong>
              <span class="status-badge status-\${proc.status}">\${proc.status}</span>
            </div>
            <div style="text-align: right; font-size: 0.9em;">
              CPU: \${proc.cpu}% | RAM: \${(proc.memory / 1024 / 1024).toFixed(1)}MB
              \${proc.port ? \`| Port: \${proc.port}\` : ''}
            </div>
          </div>
          <div style="margin-top: 5px; font-size: 0.9em; color: #94a3b8;">
            Restarts: \${proc.restarts} | Uptime: \${Math.floor(proc.uptime / 1000 / 60)}m
          </div>
        </div>
      \`).join('');
    }

    function updateLogs(logs) {
      const container = document.getElementById('logsList');
      container.innerHTML = logs.slice(0, 10).map(log => \`
        <div class="log-item \${log.error ? log.error.severity : 'info'}">
          <div style="display: flex; justify-content: space-between; align-items: flex-start;">
            <div style="flex: 1;">
              <strong>\${log.process}</strong>
              \${log.error ? \`<span class="error-count">\${log.error.category}</span>\` : ''}
              <div style="margin-top: 5px; font-size: 0.9em;">\${log.message}</div>
            </div>
            <div class="timestamp">\${new Date(log.timestamp).toLocaleTimeString()}</div>
          </div>
        </div>
      \`).join('');
    }

    function updateAlerts(alerts) {
      const activeAlerts = alerts.filter(alert => !alert.resolved);
      document.getElementById('alertCount').textContent = activeAlerts.length;
      
      const container = document.getElementById('alertsList');
      if (activeAlerts.length === 0) {
        container.innerHTML = '<div style="text-align: center; color: #22c55e; padding: 20px;">✅ No active alerts</div>';
        return;
      }
      
      container.innerHTML = activeAlerts.map(alert => \`
        <div class="alert-item \${alert.severity}">
          <div style="display: flex; justify-content: space-between; align-items: flex-start;">
            <div style="flex: 1;">
              <strong>🚨 \${alert.process}</strong>
              <span class="error-count">\${alert.severity}</span>
              <span style="margin-left: 8px; font-size: 0.9em;">[\${alert.category}]</span>
              <div style="margin-top: 5px; font-size: 0.9em;">\${alert.message}</div>
            </div>
            <div style="display: flex; flex-direction: column; align-items: flex-end; gap: 5px;">
              <div class="timestamp">\${new Date(alert.timestamp).toLocaleTimeString()}</div>
              <button class="resolve-btn" onclick="resolveAlert(\${alert.id})">Resolve</button>
            </div>
          </div>
        </div>
      \`).join('');
    }

    async function resolveAlert(alertId) {
      try {
        await fetch(\`/api/alerts/\${alertId}/resolve\`, { method: 'POST' });
        fetchLiveData(); // Refresh data
      } catch (error) {
        console.error('Error resolving alert:', error);
      }
    }

    // Start live updates
    fetchLiveData();
    updateInterval = setInterval(fetchLiveData, 3000); // Update every 3 seconds

    // Cleanup on page unload
    window.addEventListener('beforeunload', () => {
      if (updateInterval) clearInterval(updateInterval);
    });
  </script>
</body>
</html>
  `;
  res.send(html);
});

// Start data collection
updateLiveData();
setInterval(updateLiveData, 5000); // Update every 5 seconds

// Start server
app.listen(PORT, '0.0.0.0', () => {
  console.log(`🔥 Live Log Monitor running on http://45.61.58.125:${PORT}`);
  console.log('📊 Real-time system monitoring active');
  console.log('🚀 Dashboard: http://45.61.58.125:${PORT}');
});