← 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}');
});