harething commited on
Commit
9bc8e4c
·
1 Parent(s): 6552625
Files changed (1) hide show
  1. logger.js +854 -0
logger.js ADDED
@@ -0,0 +1,854 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { createServer } = require('http');
4
+ const { createWriteStream } = require('fs');
5
+ const os = require('os');
6
+
7
+ // Configuration
8
+ const LOG_DIR = process.env.LOG_DIR || '/tmp/.stremio-logs';
9
+ const LOG_RETENTION_DAYS = parseInt(process.env.LOG_RETENTION_DAYS || '7');
10
+ const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
11
+ const MAX_LOG_SIZE = parseInt(process.env.MAX_LOG_SIZE || '10485760'); // 10MB
12
+ const MONITORING_PORT = parseInt(process.env.MONITORING_PORT || '7861');
13
+
14
+ // Create log directory
15
+ if (!fs.existsSync(LOG_DIR)) {
16
+ fs.mkdirSync(LOG_DIR, { recursive: true });
17
+ }
18
+
19
+ // Setup log file paths
20
+ const logFile = path.join(LOG_DIR, 'stremio.log');
21
+ const errorLogFile = path.join(LOG_DIR, 'error.log');
22
+ const accessLogFile = path.join(LOG_DIR, 'access.log');
23
+ const metricsFile = path.join(LOG_DIR, 'metrics.json');
24
+
25
+ // Create log streams
26
+ const logStream = createWriteStream(logFile, { flags: 'a' });
27
+ const errorLogStream = createWriteStream(errorLogFile, { flags: 'a' });
28
+ const accessLogStream = createWriteStream(accessLogFile, { flags: 'a' });
29
+
30
+ // Log levels
31
+ const LOG_LEVELS = {
32
+ error: 0,
33
+ warn: 1,
34
+ info: 2,
35
+ debug: 3,
36
+ };
37
+
38
+ // In-memory logs for UI access
39
+ const memoryLogs = {
40
+ general: [],
41
+ error: [],
42
+ access: [],
43
+ MAX_ENTRIES: 1000,
44
+ };
45
+
46
+ // System metrics
47
+ let metrics = {
48
+ startTime: Date.now(),
49
+ requestsTotal: 0,
50
+ requestsSuccess: 0,
51
+ requestsError: 0,
52
+ proxyErrors: 0,
53
+ lastUpdate: Date.now(),
54
+ systemInfo: {
55
+ platform: os.platform(),
56
+ arch: os.arch(),
57
+ cpus: os.cpus().length,
58
+ totalMem: os.totalmem(),
59
+ }
60
+ };
61
+
62
+ // Save metrics periodically
63
+ const saveMetrics = () => {
64
+ metrics.lastUpdate = Date.now();
65
+ metrics.uptime = Date.now() - metrics.startTime;
66
+ metrics.memoryUsage = process.memoryUsage();
67
+ metrics.systemLoad = os.loadavg();
68
+ metrics.freeMem = os.freemem();
69
+
70
+ fs.writeFileSync(metricsFile, JSON.stringify(metrics, null, 2));
71
+ };
72
+
73
+ // Initialize metrics file
74
+ saveMetrics();
75
+ setInterval(saveMetrics, 60000); // Save every minute
76
+
77
+ // Log rotation check
78
+ const checkLogRotation = () => {
79
+ try {
80
+ const stats = fs.statSync(logFile);
81
+ if (stats.size > MAX_LOG_SIZE) {
82
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
83
+ fs.renameSync(logFile, `${logFile}.${timestamp}`);
84
+ logStream.end();
85
+ logStream = createWriteStream(logFile, { flags: 'a' });
86
+ }
87
+ } catch (err) {
88
+ console.error('Error rotating logs:', err);
89
+ }
90
+ };
91
+
92
+ // Log rotation and cleanup
93
+ setInterval(() => {
94
+ checkLogRotation();
95
+
96
+ // Clean old log files
97
+ fs.readdir(LOG_DIR, (err, files) => {
98
+ if (err) return;
99
+
100
+ const now = Date.now();
101
+ const maxAge = LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;
102
+
103
+ files.forEach(file => {
104
+ if (file.endsWith('.log.') || file.endsWith('.log-')) {
105
+ const filePath = path.join(LOG_DIR, file);
106
+ fs.stat(filePath, (err, stats) => {
107
+ if (err) return;
108
+ if (now - stats.mtime.getTime() > maxAge) {
109
+ fs.unlink(filePath, () => {});
110
+ }
111
+ });
112
+ }
113
+ });
114
+ });
115
+ }, 3600000); // Check every hour
116
+
117
+ // Logger function
118
+ const logger = {
119
+ log: (level, message, meta = {}) => {
120
+ if (LOG_LEVELS[level] > LOG_LEVELS[LOG_LEVEL]) return;
121
+
122
+ const timestamp = new Date().toISOString();
123
+ const logEntry = {
124
+ timestamp,
125
+ level,
126
+ message,
127
+ ...meta
128
+ };
129
+
130
+ const logString = JSON.stringify(logEntry);
131
+
132
+ // Write to file
133
+ logStream.write(`${logString}\n`);
134
+
135
+ // Keep in memory for UI
136
+ memoryLogs.general.unshift(logEntry);
137
+ if (memoryLogs.general.length > memoryLogs.MAX_ENTRIES) {
138
+ memoryLogs.general.pop();
139
+ }
140
+
141
+ // Also log errors to error log
142
+ if (level === 'error') {
143
+ errorLogStream.write(`${logString}\n`);
144
+
145
+ memoryLogs.error.unshift(logEntry);
146
+ if (memoryLogs.error.length > memoryLogs.MAX_ENTRIES) {
147
+ memoryLogs.error.pop();
148
+ }
149
+ }
150
+
151
+ // Console output for immediate feedback
152
+ console[level](message);
153
+ },
154
+
155
+ access: (req, res, responseTime) => {
156
+ const timestamp = new Date().toISOString();
157
+ const logEntry = {
158
+ timestamp,
159
+ method: req.method,
160
+ url: req.url,
161
+ statusCode: res.statusCode,
162
+ userAgent: req.headers['user-agent'],
163
+ responseTime,
164
+ remoteAddress: req.headers['x-forwarded-for'] || req.socket.remoteAddress
165
+ };
166
+
167
+ const logString = JSON.stringify(logEntry);
168
+
169
+ // Write to access log file
170
+ accessLogStream.write(`${logString}\n`);
171
+
172
+ // Keep in memory for UI
173
+ memoryLogs.access.unshift(logEntry);
174
+ if (memoryLogs.access.length > memoryLogs.MAX_ENTRIES) {
175
+ memoryLogs.access.pop();
176
+ }
177
+
178
+ // Update metrics
179
+ metrics.requestsTotal++;
180
+ if (res.statusCode >= 200 && res.statusCode < 400) {
181
+ metrics.requestsSuccess++;
182
+ } else {
183
+ metrics.requestsError++;
184
+ }
185
+ }
186
+ };
187
+
188
+ // Add convenience methods
189
+ logger.error = (message, meta) => logger.log('error', message, meta);
190
+ logger.warn = (message, meta) => logger.log('warn', message, meta);
191
+ logger.info = (message, meta) => logger.log('info', message, meta);
192
+ logger.debug = (message, meta) => logger.log('debug', message, meta);
193
+
194
+ // Create HTTP server for log UI and monitoring
195
+ const monitoringServer = createServer((req, res) => {
196
+ // Set CORS headers
197
+ res.setHeader('Access-Control-Allow-Origin', '*');
198
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
199
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
200
+
201
+ if (req.method === 'OPTIONS') {
202
+ res.writeHead(204);
203
+ res.end();
204
+ return;
205
+ }
206
+
207
+ // Health check endpoint
208
+ if (req.url === '/health') {
209
+ res.writeHead(200, { 'Content-Type': 'application/json' });
210
+ res.end(JSON.stringify({ status: 'up', timestamp: new Date().toISOString() }));
211
+ return;
212
+ }
213
+
214
+ // Metrics endpoint
215
+ if (req.url === '/metrics') {
216
+ res.writeHead(200, { 'Content-Type': 'application/json' });
217
+ // Update metrics before sending
218
+ metrics.uptime = Date.now() - metrics.startTime;
219
+ metrics.memoryUsage = process.memoryUsage();
220
+ metrics.systemLoad = os.loadavg();
221
+ metrics.freeMem = os.freemem();
222
+ res.end(JSON.stringify(metrics));
223
+ return;
224
+ }
225
+
226
+ // Logs endpoint
227
+ if (req.url === '/logs' || req.url.startsWith('/logs?')) {
228
+ const url = new URL(`http://localhost${req.url}`);
229
+ const type = url.searchParams.get('type') || 'general';
230
+ const limit = parseInt(url.searchParams.get('limit') || '100');
231
+
232
+ res.writeHead(200, { 'Content-Type': 'application/json' });
233
+ res.end(JSON.stringify(memoryLogs[type]?.slice(0, limit) || []));
234
+ return;
235
+ }
236
+
237
+ // UI endpoint
238
+ if (req.url === '/' || req.url === '/index.html') {
239
+ res.writeHead(200, { 'Content-Type': 'text/html' });
240
+ res.end(getLogUIHtml());
241
+ return;
242
+ }
243
+
244
+ // Not found
245
+ res.writeHead(404, { 'Content-Type': 'application/json' });
246
+ res.end(JSON.stringify({ error: 'Not found' }));
247
+ });
248
+
249
+ // Generate HTML for log UI
250
+ function getLogUIHtml() {
251
+ return `<!DOCTYPE html>
252
+ <html lang="en">
253
+ <head>
254
+ <meta charset="UTF-8">
255
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
256
+ <title>Stremio Logs & Monitoring</title>
257
+ <style>
258
+ body {
259
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
260
+ margin: 0;
261
+ padding: 0;
262
+ background: #f5f5f5;
263
+ color: #333;
264
+ }
265
+ .container {
266
+ max-width: 1200px;
267
+ margin: 0 auto;
268
+ padding: 20px;
269
+ }
270
+ header {
271
+ background: #2b2b2b;
272
+ color: white;
273
+ padding: 1rem;
274
+ display: flex;
275
+ justify-content: space-between;
276
+ align-items: center;
277
+ }
278
+ h1 {
279
+ margin: 0;
280
+ font-size: 1.5rem;
281
+ }
282
+ .tabs {
283
+ display: flex;
284
+ background: white;
285
+ margin-bottom: 20px;
286
+ border-radius: 4px;
287
+ overflow: hidden;
288
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
289
+ }
290
+ .tab {
291
+ padding: 10px 20px;
292
+ cursor: pointer;
293
+ border-bottom: 2px solid transparent;
294
+ }
295
+ .tab.active {
296
+ background: #f0f0f0;
297
+ border-bottom: 2px solid #ff6600;
298
+ }
299
+ .card {
300
+ background: white;
301
+ border-radius: 4px;
302
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
303
+ margin-bottom: 20px;
304
+ padding: 20px;
305
+ }
306
+ .card h2 {
307
+ margin-top: 0;
308
+ border-bottom: 1px solid #eee;
309
+ padding-bottom: 10px;
310
+ }
311
+ .metrics {
312
+ display: grid;
313
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
314
+ gap: 15px;
315
+ }
316
+ .metric-card {
317
+ background: #f9f9f9;
318
+ padding: 15px;
319
+ border-radius: 4px;
320
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
321
+ }
322
+ .metric-title {
323
+ font-size: 0.9rem;
324
+ color: #666;
325
+ margin-bottom: 5px;
326
+ }
327
+ .metric-value {
328
+ font-size: 1.4rem;
329
+ font-weight: bold;
330
+ }
331
+ table {
332
+ width: 100%;
333
+ border-collapse: collapse;
334
+ }
335
+ table th, table td {
336
+ text-align: left;
337
+ padding: 8px;
338
+ border-bottom: 1px solid #eee;
339
+ }
340
+ table th {
341
+ background: #f0f0f0;
342
+ }
343
+ .log-row {
344
+ font-family: monospace;
345
+ font-size: 0.9rem;
346
+ }
347
+ .log-row.error {
348
+ background-color: #ffecec;
349
+ }
350
+ .log-row.warn {
351
+ background-color: #fffbec;
352
+ }
353
+ .controls {
354
+ margin-bottom: 15px;
355
+ display: flex;
356
+ justify-content: space-between;
357
+ align-items: center;
358
+ }
359
+ select, button {
360
+ padding: 8px 12px;
361
+ border-radius: 4px;
362
+ border: 1px solid #ddd;
363
+ background: white;
364
+ }
365
+ button {
366
+ cursor: pointer;
367
+ }
368
+ button:hover {
369
+ background: #f0f0f0;
370
+ }
371
+ .log-count {
372
+ font-size: 0.9rem;
373
+ color: #666;
374
+ }
375
+ .timestamp {
376
+ font-size: 0.8rem;
377
+ color: #888;
378
+ }
379
+ .status-indicator {
380
+ display: inline-block;
381
+ width: 10px;
382
+ height: 10px;
383
+ border-radius: 50%;
384
+ margin-right: 5px;
385
+ }
386
+ .status-up {
387
+ background: #4caf50;
388
+ }
389
+ .status-down {
390
+ background: #f44336;
391
+ }
392
+ #lastUpdated {
393
+ font-size: 0.8rem;
394
+ color: #888;
395
+ }
396
+ </style>
397
+ </head>
398
+ <body>
399
+ <header>
400
+ <h1>Stremio Logs & Monitoring</h1>
401
+ <div>
402
+ <span class="status-indicator status-up" id="statusIndicator"></span>
403
+ <span id="statusText">System Online</span>
404
+ </div>
405
+ </header>
406
+
407
+ <div class="container">
408
+ <div class="tabs">
409
+ <div class="tab active" data-tab="dashboard">Dashboard</div>
410
+ <div class="tab" data-tab="logs">Logs</div>
411
+ <div class="tab" data-tab="access">Access Logs</div>
412
+ <div class="tab" data-tab="errors">Error Logs</div>
413
+ <div class="tab" data-tab="settings">Settings</div>
414
+ </div>
415
+
416
+ <div id="dashboard" class="tab-content">
417
+ <div class="card">
418
+ <h2>System Overview</h2>
419
+ <div class="metrics" id="systemMetrics">
420
+ <!-- Metrics will be inserted here -->
421
+ </div>
422
+ </div>
423
+
424
+ <div class="card">
425
+ <h2>Request Statistics</h2>
426
+ <div class="metrics" id="requestMetrics">
427
+ <!-- Request metrics will be inserted here -->
428
+ </div>
429
+ </div>
430
+
431
+ <div class="card">
432
+ <h2>Recent Logs</h2>
433
+ <div id="recentLogs">
434
+ <!-- Recent logs will be inserted here -->
435
+ </div>
436
+ </div>
437
+ </div>
438
+
439
+ <div id="logs" class="tab-content" style="display:none">
440
+ <div class="card">
441
+ <h2>General Logs</h2>
442
+ <div class="controls">
443
+ <div>
444
+ <select id="logLevel">
445
+ <option value="all">All Levels</option>
446
+ <option value="error">Error</option>
447
+ <option value="warn">Warning</option>
448
+ <option value="info">Info</option>
449
+ <option value="debug">Debug</option>
450
+ </select>
451
+ <button id="refreshLogs">Refresh</button>
452
+ </div>
453
+ <div class="log-count"><span id="logCount">0</span> logs</div>
454
+ </div>
455
+ <div id="logTableContainer" style="max-height: 600px; overflow-y: auto;">
456
+ <table id="logTable">
457
+ <thead>
458
+ <tr>
459
+ <th>Timestamp</th>
460
+ <th>Level</th>
461
+ <th>Message</th>
462
+ </tr>
463
+ </thead>
464
+ <tbody>
465
+ <!-- Logs will be inserted here -->
466
+ </tbody>
467
+ </table>
468
+ </div>
469
+ </div>
470
+ </div>
471
+
472
+ <div id="access" class="tab-content" style="display:none">
473
+ <div class="card">
474
+ <h2>Access Logs</h2>
475
+ <div class="controls">
476
+ <div>
477
+ <select id="statusFilter">
478
+ <option value="all">All Status Codes</option>
479
+ <option value="200">200 Success</option>
480
+ <option value="300">300 Redirects</option>
481
+ <option value="400">400 Client Errors</option>
482
+ <option value="500">500 Server Errors</option>
483
+ </select>
484
+ <button id="refreshAccessLogs">Refresh</button>
485
+ </div>
486
+ <div class="log-count"><span id="accessLogCount">0</span> requests</div>
487
+ </div>
488
+ <div id="accessLogTableContainer" style="max-height: 600px; overflow-y: auto;">
489
+ <table id="accessLogTable">
490
+ <thead>
491
+ <tr>
492
+ <th>Timestamp</th>
493
+ <th>Method</th>
494
+ <th>URL</th>
495
+ <th>Status</th>
496
+ <th>Response Time</th>
497
+ </tr>
498
+ </thead>
499
+ <tbody>
500
+ <!-- Access logs will be inserted here -->
501
+ </tbody>
502
+ </table>
503
+ </div>
504
+ </div>
505
+ </div>
506
+
507
+ <div id="errors" class="tab-content" style="display:none">
508
+ <div class="card">
509
+ <h2>Error Logs</h2>
510
+ <div class="controls">
511
+ <div>
512
+ <button id="refreshErrorLogs">Refresh</button>
513
+ </div>
514
+ <div class="log-count"><span id="errorLogCount">0</span> errors</div>
515
+ </div>
516
+ <div id="errorLogTableContainer" style="max-height: 600px; overflow-y: auto;">
517
+ <table id="errorLogTable">
518
+ <thead>
519
+ <tr>
520
+ <th>Timestamp</th>
521
+ <th>Message</th>
522
+ <th>Details</th>
523
+ </tr>
524
+ </thead>
525
+ <tbody>
526
+ <!-- Error logs will be inserted here -->
527
+ </tbody>
528
+ </table>
529
+ </div>
530
+ </div>
531
+ </div>
532
+
533
+ <div id="settings" class="tab-content" style="display:none">
534
+ <div class="card">
535
+ <h2>Settings</h2>
536
+ <p>Log settings are configured via environment variables:</p>
537
+ <table>
538
+ <tr>
539
+ <th>Setting</th>
540
+ <th>Current Value</th>
541
+ <th>Description</th>
542
+ </tr>
543
+ <tr>
544
+ <td>LOG_LEVEL</td>
545
+ <td id="settingLogLevel"></td>
546
+ <td>Minimum log level to record</td>
547
+ </tr>
548
+ <tr>
549
+ <td>LOG_DIR</td>
550
+ <td id="settingLogDir"></td>
551
+ <td>Directory where logs are stored</td>
552
+ </tr>
553
+ <tr>
554
+ <td>LOG_RETENTION_DAYS</td>
555
+ <td id="settingRetention"></td>
556
+ <td>Number of days to keep log files</td>
557
+ </tr>
558
+ <tr>
559
+ <td>MAX_LOG_SIZE</td>
560
+ <td id="settingMaxSize"></td>
561
+ <td>Maximum size of log file before rotation</td>
562
+ </tr>
563
+ </table>
564
+ </div>
565
+ </div>
566
+ </div>
567
+
568
+ <div style="text-align: center; margin-top: 20px; padding-bottom: 20px;">
569
+ <div id="lastUpdated"></div>
570
+ </div>
571
+
572
+ <script>
573
+ // Element references
574
+ const tabs = document.querySelectorAll('.tab');
575
+ const tabContents = document.querySelectorAll('.tab-content');
576
+ const logTable = document.getElementById('logTable').querySelector('tbody');
577
+ const accessLogTable = document.getElementById('accessLogTable').querySelector('tbody');
578
+ const errorLogTable = document.getElementById('errorLogTable').querySelector('tbody');
579
+ const systemMetricsEl = document.getElementById('systemMetrics');
580
+ const requestMetricsEl = document.getElementById('requestMetrics');
581
+ const recentLogsEl = document.getElementById('recentLogs');
582
+ const lastUpdatedEl = document.getElementById('lastUpdated');
583
+ const logLevelSelect = document.getElementById('logLevel');
584
+ const statusFilterSelect = document.getElementById('statusFilter');
585
+ const refreshLogsBtn = document.getElementById('refreshLogs');
586
+ const refreshAccessLogsBtn = document.getElementById('refreshAccessLogs');
587
+ const refreshErrorLogsBtn = document.getElementById('refreshErrorLogs');
588
+ const logCountEl = document.getElementById('logCount');
589
+ const accessLogCountEl = document.getElementById('accessLogCount');
590
+ const errorLogCountEl = document.getElementById('errorLogCount');
591
+ const settingLogLevelEl = document.getElementById('settingLogLevel');
592
+ const settingLogDirEl = document.getElementById('settingLogDir');
593
+ const settingRetentionEl = document.getElementById('settingRetention');
594
+ const settingMaxSizeEl = document.getElementById('settingMaxSize');
595
+ const statusIndicator = document.getElementById('statusIndicator');
596
+ const statusText = document.getElementById('statusText');
597
+
598
+ // Tab switching
599
+ tabs.forEach(tab => {
600
+ tab.addEventListener('click', () => {
601
+ tabs.forEach(t => t.classList.remove('active'));
602
+ tab.classList.add('active');
603
+
604
+ const tabName = tab.getAttribute('data-tab');
605
+ tabContents.forEach(content => {
606
+ content.style.display = content.id === tabName ? 'block' : 'none';
607
+ });
608
+ });
609
+ });
610
+
611
+ // Format date
612
+ function formatDate(dateString) {
613
+ const date = new Date(dateString);
614
+ return date.toLocaleString();
615
+ }
616
+
617
+ // Format bytes
618
+ function formatBytes(bytes, decimals = 2) {
619
+ if (bytes === 0) return '0 Bytes';
620
+ const k = 1024;
621
+ const dm = decimals < 0 ? 0 : decimals;
622
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
623
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
624
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
625
+ }
626
+
627
+ // Format duration
628
+ function formatDuration(ms) {
629
+ const seconds = Math.floor(ms / 1000);
630
+ const minutes = Math.floor(seconds / 60);
631
+ const hours = Math.floor(minutes / 60);
632
+ const days = Math.floor(hours / 24);
633
+
634
+ if (days > 0) return days + 'd ' + (hours % 24) + 'h';
635
+ if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm';
636
+ if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's';
637
+ return seconds + 's';
638
+ }
639
+
640
+ // Fetch metrics
641
+ async function fetchMetrics() {
642
+ try {
643
+ const response = await fetch('/metrics');
644
+ const metrics = await response.json();
645
+
646
+ // Update system metrics
647
+ let systemMetricsHtml = '';
648
+
649
+ systemMetricsHtml += createMetricCard('Uptime', formatDuration(metrics.uptime));
650
+ systemMetricsHtml += createMetricCard('Platform', metrics.systemInfo.platform);
651
+ systemMetricsHtml += createMetricCard('CPU Cores', metrics.systemInfo.cpus);
652
+ systemMetricsHtml += createMetricCard('Memory Total', formatBytes(metrics.systemInfo.totalMem));
653
+ systemMetricsHtml += createMetricCard('Memory Free', formatBytes(metrics.freeMem));
654
+ systemMetricsHtml += createMetricCard('Memory Usage', formatBytes(metrics.memoryUsage.rss));
655
+ systemMetricsHtml += createMetricCard('Heap Used', formatBytes(metrics.memoryUsage.heapUsed));
656
+ systemMetricsHtml += createMetricCard('CPU Load', metrics.systemLoad[0].toFixed(2));
657
+
658
+ systemMetricsEl.innerHTML = systemMetricsHtml;
659
+
660
+ // Update request metrics
661
+ let requestMetricsHtml = '';
662
+
663
+ requestMetricsHtml += createMetricCard('Total Requests', metrics.requestsTotal);
664
+ requestMetricsHtml += createMetricCard('Successful', metrics.requestsSuccess);
665
+ requestMetricsHtml += createMetricCard('Errors', metrics.requestsError);
666
+ requestMetricsHtml += createMetricCard('Success Rate', ((metrics.requestsSuccess / metrics.requestsTotal) * 100 || 0).toFixed(1) + '%');
667
+ requestMetricsHtml += createMetricCard('Proxy Errors', metrics.proxyErrors);
668
+
669
+ requestMetricsEl.innerHTML = requestMetricsHtml;
670
+
671
+ // Update settings
672
+ settingLogLevelEl.textContent = '${LOG_LEVEL}';
673
+ settingLogDirEl.textContent = '${LOG_DIR}';
674
+ settingRetentionEl.textContent = '${LOG_RETENTION_DAYS} days';
675
+ settingMaxSizeEl.textContent = formatBytes(${MAX_LOG_SIZE});
676
+
677
+ // Update system status
678
+ statusIndicator.className = 'status-indicator status-up';
679
+ statusText.textContent = 'System Online';
680
+
681
+ lastUpdatedEl.textContent = 'Last updated: ' + new Date().toLocaleString();
682
+ } catch (err) {
683
+ console.error('Error fetching metrics:', err);
684
+ statusIndicator.className = 'status-indicator status-down';
685
+ statusText.textContent = 'System Error';
686
+ }
687
+ }
688
+
689
+ // Create metric card
690
+ function createMetricCard(title, value) {
691
+ return \`
692
+ <div class="metric-card">
693
+ <div class="metric-title">\${title}</div>
694
+ <div class="metric-value">\${value}</div>
695
+ </div>
696
+ \`;
697
+ }
698
+
699
+ // Fetch and render logs
700
+ async function fetchLogs(type = 'general', filter = 'all') {
701
+ try {
702
+ const response = await fetch(\`/logs?type=\${type}\`);
703
+ const logs = await response.json();
704
+
705
+ let filteredLogs = logs;
706
+ let tableEl;
707
+ let countEl;
708
+
709
+ if (type === 'general') {
710
+ if (filter !== 'all') {
711
+ filteredLogs = logs.filter(log => log.level === filter);
712
+ }
713
+ tableEl = logTable;
714
+ countEl = logCountEl;
715
+ } else if (type === 'access') {
716
+ if (filter !== 'all') {
717
+ const statusPrefix = filter.charAt(0);
718
+ filteredLogs = logs.filter(log =>
719
+ log.statusCode.toString().charAt(0) === statusPrefix);
720
+ }
721
+ tableEl = accessLogTable;
722
+ countEl = accessLogCountEl;
723
+ } else if (type === 'error') {
724
+ tableEl = errorLogTable;
725
+ countEl = errorLogCountEl;
726
+ }
727
+
728
+ // Update count
729
+ countEl.textContent = filteredLogs.length;
730
+
731
+ // Render logs
732
+ let html = '';
733
+
734
+ if (type === 'general') {
735
+ filteredLogs.forEach(log => {
736
+ html += \`
737
+ <tr class="log-row \${log.level}">
738
+ <td class="timestamp">\${formatDate(log.timestamp)}</td>
739
+ <td>\${log.level.toUpperCase()}</td>
740
+ <td>\${log.message}</td>
741
+ </tr>
742
+ \`;
743
+ });
744
+ } else if (type === 'access') {
745
+ filteredLogs.forEach(log => {
746
+ const statusClass = log.statusCode >= 400 ? 'error' :
747
+ log.statusCode >= 300 ? 'warn' : '';
748
+ html += \`
749
+ <tr class="log-row \${statusClass}">
750
+ <td class="timestamp">\${formatDate(log.timestamp)}</td>
751
+ <td>\${log.method}</td>
752
+ <td>\${log.url}</td>
753
+ <td>\${log.statusCode}</td>
754
+ <td>\${log.responseTime}ms</td>
755
+ </tr>
756
+ \`;
757
+ });
758
+ } else if (type === 'error') {
759
+ filteredLogs.forEach(log => {
760
+ html += \`
761
+ <tr class="log-row error">
762
+ <td class="timestamp">\${formatDate(log.timestamp)}</td>
763
+ <td>\${log.message}</td>
764
+ <td>\${JSON.stringify(log.meta || {})}</td>
765
+ </tr>
766
+ \`;
767
+ });
768
+ }
769
+
770
+ tableEl.innerHTML = html;
771
+
772
+ // Also update recent logs on dashboard
773
+ if (type === 'general' && recentLogsEl) {
774
+ let recentHtml = '<table><thead><tr><th>Time</th><th>Level</th><th>Message</th></tr></thead><tbody>';
775
+ logs.slice(0, 5).forEach(log => {
776
+ recentHtml += \`
777
+ <tr class="log-row \${log.level}">
778
+ <td class="timestamp">\${formatDate(log.timestamp)}</td>
779
+ <td>\${log.level.toUpperCase()}</td>
780
+ <td>\${log.message}</td>
781
+ </tr>
782
+ \`;
783
+ });
784
+ recentHtml += '</tbody></table>';
785
+ recentLogsEl.innerHTML = recentHtml;
786
+ }
787
+ } catch (err) {
788
+ console.error(\`Error fetching \${type} logs:\`, err);
789
+ }
790
+ }
791
+
792
+ // Event listeners
793
+ refreshLogsBtn.addEventListener('click', () => {
794
+ fetchLogs('general', logLevelSelect.value);
795
+ });
796
+
797
+ refreshAccessLogsBtn.addEventListener('click', () => {
798
+ fetchLogs('access', statusFilterSelect.value);
799
+ });
800
+
801
+ refreshErrorLogsBtn.addEventListener('click', () => {
802
+ fetchLogs('error');
803
+ });
804
+
805
+ logLevelSelect.addEventListener('change', () => {
806
+ fetchLogs('general', logLevelSelect.value);
807
+ });
808
+
809
+ statusFilterSelect.addEventListener('change', () => {
810
+ fetchLogs('access', statusFilterSelect.value);
811
+ });
812
+
813
+ // Initial data fetch
814
+ fetchMetrics();
815
+ fetchLogs('general');
816
+ fetchLogs('access');
817
+ fetchLogs('error');
818
+
819
+ // Refresh data periodically
820
+ setInterval(fetchMetrics, 10000);
821
+ setInterval(() => fetchLogs('general', logLevelSelect.value), 10000);
822
+ setInterval(() => fetchLogs('access', statusFilterSelect.value), 10000);
823
+ setInterval(() => fetchLogs('error'), 10000);
824
+ </script>
825
+ </body>
826
+ </html>`;
827
+ }
828
+
829
+ // Start monitoring server
830
+ monitoringServer.listen(MONITORING_PORT, () => {
831
+ logger.info(`Monitoring server listening on port ${MONITORING_PORT}`, { service: 'logger' });
832
+ });
833
+
834
+ // Handle exit
835
+ process.on('SIGINT', () => {
836
+ logger.info('Received SIGINT, shutting down...', { service: 'logger' });
837
+ logStream.end();
838
+ errorLogStream.end();
839
+ accessLogStream.end();
840
+ monitoringServer.close();
841
+ process.exit(0);
842
+ });
843
+
844
+ process.on('SIGTERM', () => {
845
+ logger.info('Received SIGTERM, shutting down...', { service: 'logger' });
846
+ logStream.end();
847
+ errorLogStream.end();
848
+ accessLogStream.end();
849
+ monitoringServer.close();
850
+ process.exit(0);
851
+ });
852
+
853
+ // Export logger
854
+ module.exports = logger;