pms_md 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +93 -0
  2. package/node-monitor/ARCHITECTURE.md +341 -0
  3. package/node-monitor/CHANGELOG.md +105 -0
  4. package/node-monitor/CONTRIBUTING.md +96 -0
  5. package/node-monitor/DESIGN_IMPROVEMENTS.md +286 -0
  6. package/node-monitor/FILTER_BUTTONS_FIX.md +303 -0
  7. package/node-monitor/GETTING_STARTED.md +416 -0
  8. package/node-monitor/INSTALLATION.md +470 -0
  9. package/node-monitor/LICENSE +22 -0
  10. package/node-monitor/PUBLISHING_GUIDE.md +331 -0
  11. package/node-monitor/QUICK_REFERENCE.md +252 -0
  12. package/node-monitor/README.md +458 -0
  13. package/node-monitor/READY_TO_PUBLISH.md +272 -0
  14. package/node-monitor/SETUP_GUIDE.md +479 -0
  15. package/node-monitor/examples/EMAIL_SETUP_GUIDE.md +282 -0
  16. package/node-monitor/examples/ERROR_LOGGING_GUIDE.md +405 -0
  17. package/node-monitor/examples/GET_APP_PASSWORD.md +145 -0
  18. package/node-monitor/examples/LOG_FILES_REFERENCE.md +336 -0
  19. package/node-monitor/examples/QUICK_START_EMAIL.md +126 -0
  20. package/node-monitor/examples/express-app.js +499 -0
  21. package/node-monitor/examples/package-lock.json +1295 -0
  22. package/node-monitor/examples/package.json +18 -0
  23. package/node-monitor/examples/public/css/style.css +718 -0
  24. package/node-monitor/examples/public/js/dashboard.js +207 -0
  25. package/node-monitor/examples/public/js/health.js +114 -0
  26. package/node-monitor/examples/public/js/main.js +89 -0
  27. package/node-monitor/examples/public/js/metrics.js +225 -0
  28. package/node-monitor/examples/public/js/theme.js +138 -0
  29. package/node-monitor/examples/views/dashboard.ejs +20 -0
  30. package/node-monitor/examples/views/error-logs.ejs +1129 -0
  31. package/node-monitor/examples/views/health.ejs +21 -0
  32. package/node-monitor/examples/views/home.ejs +341 -0
  33. package/node-monitor/examples/views/layout.ejs +50 -0
  34. package/node-monitor/examples/views/metrics.ejs +16 -0
  35. package/node-monitor/examples/views/partials/footer.ejs +16 -0
  36. package/node-monitor/examples/views/partials/header.ejs +35 -0
  37. package/node-monitor/examples/views/partials/nav.ejs +23 -0
  38. package/node-monitor/examples/views/status.ejs +390 -0
  39. package/node-monitor/package-lock.json +4300 -0
  40. package/node-monitor/package.json +76 -0
  41. package/node-monitor/pre-publish-check.js +200 -0
  42. package/node-monitor/src/config/monitoringConfig.js +255 -0
  43. package/node-monitor/src/index.js +300 -0
  44. package/node-monitor/src/logger/errorLogger.js +297 -0
  45. package/node-monitor/src/monitors/apiErrorMonitor.js +156 -0
  46. package/node-monitor/src/monitors/dbConnectionMonitor.js +389 -0
  47. package/node-monitor/src/monitors/serverHealthMonitor.js +320 -0
  48. package/node-monitor/src/monitors/systemResourceMonitor.js +357 -0
  49. package/node-monitor/src/notifiers/emailNotifier.js +248 -0
  50. package/node-monitor/src/notifiers/notificationManager.js +96 -0
  51. package/node-monitor/src/notifiers/slackNotifier.js +209 -0
  52. package/node-monitor/src/views/dashboard.html +530 -0
  53. package/node-monitor/src/views/health.html +399 -0
  54. package/node-monitor/src/views/metrics.html +406 -0
  55. package/package.json +22 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * API Error Monitor
3
+ * Express/Fastify middleware for tracking API errors
4
+ */
5
+
6
+ class ApiErrorMonitor {
7
+ constructor(config, logger, notificationManager) {
8
+ this.config = config;
9
+ this.logger = logger;
10
+ this.notificationManager = notificationManager;
11
+ this.errorThreshold = config.thresholds.errorRate;
12
+ this.lastAlertTime = 0;
13
+ }
14
+
15
+ /**
16
+ * Express middleware for error tracking
17
+ */
18
+ middleware() {
19
+ return (err, req, res, next) => {
20
+ // Log the error
21
+ const errorData = this.logger.logApiError(err, req);
22
+
23
+ // Check error rate and send alert if threshold exceeded
24
+ this.checkErrorRate();
25
+
26
+ // Send response if not already sent
27
+ if (!res.headersSent) {
28
+ const statusCode = err.statusCode || err.status || 500;
29
+ res.status(statusCode).json({
30
+ error: {
31
+ message: err.message || 'Internal Server Error',
32
+ statusCode: statusCode,
33
+ ...(this.config.app.environment === 'development' && {
34
+ stack: err.stack
35
+ })
36
+ }
37
+ });
38
+ }
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Request logger middleware (tracks all requests)
44
+ */
45
+ requestLogger() {
46
+ return (req, res, next) => {
47
+ const startTime = Date.now();
48
+
49
+ // Capture response
50
+ const originalSend = res.send;
51
+ res.send = function(data) {
52
+ res.send = originalSend;
53
+ const responseTime = Date.now() - startTime;
54
+
55
+ // Log slow requests
56
+ const threshold = this.config.thresholds.responseTime;
57
+ if (responseTime > threshold) {
58
+ this.logger.logWarning('slow_request', 'Slow API response detected', {
59
+ method: req.method,
60
+ url: req.originalUrl || req.url,
61
+ responseTime: `${responseTime}ms`,
62
+ threshold: `${threshold}ms`,
63
+ statusCode: res.statusCode
64
+ });
65
+ }
66
+
67
+ return res.send(data);
68
+ }.bind(this);
69
+
70
+ next();
71
+ };
72
+ }
73
+
74
+ /**
75
+ * 404 handler middleware
76
+ */
77
+ notFoundHandler() {
78
+ return (req, res, next) => {
79
+ this.logger.logWarning('not_found', '404 - Route not found', {
80
+ method: req.method,
81
+ url: req.originalUrl || req.url,
82
+ ip: req.ip || req.connection?.remoteAddress
83
+ });
84
+
85
+ res.status(404).json({
86
+ error: {
87
+ message: 'Route not found',
88
+ statusCode: 404,
89
+ path: req.originalUrl || req.url
90
+ }
91
+ });
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Check error rate and send alert if threshold exceeded
97
+ */
98
+ async checkErrorRate() {
99
+ const currentRate = this.logger.getTotalErrorRate();
100
+
101
+ if (currentRate >= this.errorThreshold) {
102
+ const now = Date.now();
103
+ const cooldown = this.config.notifications.cooldown;
104
+
105
+ // Only send alert if cooldown period has passed
106
+ if (now - this.lastAlertTime >= cooldown) {
107
+ await this.notificationManager.sendWarning(
108
+ 'High Error Rate Detected',
109
+ `Application is experiencing a high error rate: ${currentRate} errors per minute`,
110
+ {
111
+ errorRate: `${currentRate} errors/min`,
112
+ threshold: `${this.errorThreshold} errors/min`,
113
+ application: this.config.app.name,
114
+ environment: this.config.app.environment
115
+ }
116
+ );
117
+
118
+ this.lastAlertTime = now;
119
+ }
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Get error statistics
125
+ */
126
+ getStats() {
127
+ return {
128
+ currentErrorRate: this.logger.getTotalErrorRate(),
129
+ threshold: this.errorThreshold,
130
+ apiErrorRate: this.logger.getErrorRate('api_error')
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Async error wrapper for route handlers
136
+ * Usage: app.get('/route', asyncHandler(async (req, res) => { ... }))
137
+ */
138
+ asyncHandler(fn) {
139
+ return (req, res, next) => {
140
+ Promise.resolve(fn(req, res, next)).catch(next);
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Create error with status code
146
+ */
147
+ static createError(message, statusCode = 500, details = {}) {
148
+ const error = new Error(message);
149
+ error.statusCode = statusCode;
150
+ error.details = details;
151
+ return error;
152
+ }
153
+ }
154
+
155
+ module.exports = ApiErrorMonitor;
156
+
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Database Connection Monitor
3
+ * Monitors database connections for MongoDB, PostgreSQL, MySQL, and Redis
4
+ */
5
+
6
+ const cron = require('node-cron');
7
+
8
+ class DbConnectionMonitor {
9
+ constructor(config, logger, notificationManager) {
10
+ this.config = config;
11
+ this.logger = logger;
12
+ this.notificationManager = notificationManager;
13
+ this.connections = new Map();
14
+ this.cronJob = null;
15
+ this.connectionStatus = new Map();
16
+ this.consecutiveFailures = new Map();
17
+ }
18
+
19
+ /**
20
+ * Start database monitoring
21
+ */
22
+ start() {
23
+ if (!this.config.database.enabled || this.connections.size === 0) {
24
+ return;
25
+ }
26
+
27
+ this.logger.logInfo('Database connection monitoring started');
28
+
29
+ // Schedule periodic checks
30
+ const intervalMinutes = Math.max(1, Math.floor(this.config.intervals.database / 60000));
31
+ const cronExpression = `*/${intervalMinutes} * * * *`;
32
+
33
+ this.cronJob = cron.schedule(cronExpression, async () => {
34
+ await this.checkAllConnections();
35
+ });
36
+
37
+ // Perform initial check
38
+ this.checkAllConnections();
39
+ }
40
+
41
+ /**
42
+ * Stop database monitoring
43
+ */
44
+ stop() {
45
+ if (this.cronJob) {
46
+ this.cronJob.stop();
47
+ this.logger.logInfo('Database connection monitoring stopped');
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Register a database connection
53
+ */
54
+ registerConnection(name, type, connection, testQuery = null) {
55
+ this.connections.set(name, {
56
+ type,
57
+ connection,
58
+ testQuery
59
+ });
60
+
61
+ this.connectionStatus.set(name, true);
62
+ this.consecutiveFailures.set(name, 0);
63
+
64
+ this.logger.logInfo(`Registered database connection: ${name} (${type})`);
65
+ }
66
+
67
+ /**
68
+ * Check all database connections
69
+ */
70
+ async checkAllConnections() {
71
+ const results = {};
72
+
73
+ for (const [name, dbConfig] of this.connections) {
74
+ try {
75
+ const isConnected = await this.checkConnection(name, dbConfig);
76
+ results[name] = {
77
+ status: isConnected ? 'connected' : 'disconnected',
78
+ type: dbConfig.type
79
+ };
80
+
81
+ await this.handleConnectionStatus(name, isConnected, dbConfig.type);
82
+ } catch (error) {
83
+ results[name] = {
84
+ status: 'error',
85
+ type: dbConfig.type,
86
+ error: error.message
87
+ };
88
+
89
+ await this.handleConnectionStatus(name, false, dbConfig.type, error);
90
+ }
91
+ }
92
+
93
+ this.logger.logInfo('Database connection check completed', results);
94
+ return results;
95
+ }
96
+
97
+ /**
98
+ * Check individual database connection
99
+ */
100
+ async checkConnection(name, dbConfig) {
101
+ const { type, connection, testQuery } = dbConfig;
102
+
103
+ switch (type.toLowerCase()) {
104
+ case 'mongodb':
105
+ case 'mongoose':
106
+ return this.checkMongoConnection(connection);
107
+
108
+ case 'postgresql':
109
+ case 'postgres':
110
+ case 'pg':
111
+ return this.checkPostgresConnection(connection, testQuery);
112
+
113
+ case 'mysql':
114
+ return this.checkMySqlConnection(connection, testQuery);
115
+
116
+ case 'redis':
117
+ return this.checkRedisConnection(connection);
118
+
119
+ default:
120
+ throw new Error(`Unsupported database type: ${type}`);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Check MongoDB/Mongoose connection
126
+ */
127
+ async checkMongoConnection(connection) {
128
+ // For Mongoose
129
+ if (connection.readyState !== undefined) {
130
+ return connection.readyState === 1; // 1 = connected
131
+ }
132
+
133
+ // For native MongoDB driver
134
+ if (connection.topology) {
135
+ return connection.topology.isConnected();
136
+ }
137
+
138
+ // Try ping command
139
+ try {
140
+ await connection.db().admin().ping();
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Check PostgreSQL connection
149
+ */
150
+ async checkPostgresConnection(connection, testQuery = 'SELECT 1') {
151
+ try {
152
+ const result = await Promise.race([
153
+ connection.query(testQuery),
154
+ this.timeout(this.config.database.queryTimeout)
155
+ ]);
156
+ return !!result;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Check MySQL connection
164
+ */
165
+ async checkMySqlConnection(connection, testQuery = 'SELECT 1') {
166
+ try {
167
+ const [rows] = await Promise.race([
168
+ connection.query(testQuery),
169
+ this.timeout(this.config.database.queryTimeout)
170
+ ]);
171
+ return !!rows;
172
+ } catch {
173
+ return false;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Check Redis connection
179
+ */
180
+ async checkRedisConnection(connection) {
181
+ try {
182
+ const result = await Promise.race([
183
+ connection.ping(),
184
+ this.timeout(this.config.database.queryTimeout)
185
+ ]);
186
+ return result === 'PONG';
187
+ } catch {
188
+ return false;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Handle connection status changes
194
+ */
195
+ async handleConnectionStatus(name, isConnected, type, error = null) {
196
+ const previousStatus = this.connectionStatus.get(name);
197
+ this.connectionStatus.set(name, isConnected);
198
+
199
+ if (!isConnected) {
200
+ const failures = this.consecutiveFailures.get(name) + 1;
201
+ this.consecutiveFailures.set(name, failures);
202
+
203
+ // Send alert if threshold reached
204
+ if (failures >= this.config.thresholds.consecutiveFailures) {
205
+ if (previousStatus) {
206
+ // Status changed from connected to disconnected
207
+ await this.notificationManager.sendCritical(
208
+ 'Database Connection Lost',
209
+ `Connection to ${name} (${type}) has been lost`,
210
+ {
211
+ database: name,
212
+ type: type,
213
+ consecutiveFailures: failures,
214
+ error: error?.message || 'Connection check failed'
215
+ }
216
+ );
217
+ }
218
+ }
219
+
220
+ this.logger.logSystemError('db_connection_failed', `Database connection check failed: ${name}`, {
221
+ database: name,
222
+ type: type,
223
+ consecutiveFailures: failures,
224
+ error: error?.message
225
+ });
226
+ } else {
227
+ const previousFailures = this.consecutiveFailures.get(name);
228
+
229
+ // Recovery notification
230
+ if (!previousStatus && previousFailures >= this.config.thresholds.consecutiveFailures) {
231
+ await this.notificationManager.sendRecovery(
232
+ 'Database Connection Restored',
233
+ `Connection to ${name} (${type}) has been restored`,
234
+ {
235
+ database: name,
236
+ type: type,
237
+ previousFailures: previousFailures
238
+ }
239
+ );
240
+ }
241
+
242
+ this.consecutiveFailures.set(name, 0);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Get connection status for all databases
248
+ */
249
+ getStatus() {
250
+ const status = {};
251
+
252
+ for (const [name, dbConfig] of this.connections) {
253
+ status[name] = {
254
+ type: dbConfig.type,
255
+ connected: this.connectionStatus.get(name),
256
+ consecutiveFailures: this.consecutiveFailures.get(name)
257
+ };
258
+ }
259
+
260
+ return status;
261
+ }
262
+
263
+ /**
264
+ * Get connection statistics
265
+ */
266
+ async getStats(name) {
267
+ const dbConfig = this.connections.get(name);
268
+ if (!dbConfig) {
269
+ throw new Error(`Database connection not found: ${name}`);
270
+ }
271
+
272
+ const { type, connection } = dbConfig;
273
+
274
+ switch (type.toLowerCase()) {
275
+ case 'mongodb':
276
+ case 'mongoose':
277
+ return this.getMongoStats(connection);
278
+
279
+ case 'postgresql':
280
+ case 'postgres':
281
+ case 'pg':
282
+ return this.getPostgresStats(connection);
283
+
284
+ case 'mysql':
285
+ return this.getMySqlStats(connection);
286
+
287
+ case 'redis':
288
+ return this.getRedisStats(connection);
289
+
290
+ default:
291
+ return { type, status: 'unknown' };
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Get MongoDB statistics
297
+ */
298
+ async getMongoStats(connection) {
299
+ try {
300
+ const db = connection.db ? connection.db() : connection;
301
+ const stats = await db.stats();
302
+
303
+ return {
304
+ type: 'mongodb',
305
+ collections: stats.collections,
306
+ dataSize: stats.dataSize,
307
+ indexes: stats.indexes,
308
+ indexSize: stats.indexSize
309
+ };
310
+ } catch (error) {
311
+ return { type: 'mongodb', error: error.message };
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Get PostgreSQL statistics
317
+ */
318
+ async getPostgresStats(connection) {
319
+ try {
320
+ const result = await connection.query(`
321
+ SELECT count(*) as connections
322
+ FROM pg_stat_activity
323
+ WHERE datname = current_database()
324
+ `);
325
+
326
+ return {
327
+ type: 'postgresql',
328
+ activeConnections: result.rows[0].connections
329
+ };
330
+ } catch (error) {
331
+ return { type: 'postgresql', error: error.message };
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Get MySQL statistics
337
+ */
338
+ async getMySqlStats(connection) {
339
+ try {
340
+ const [rows] = await connection.query('SHOW STATUS LIKE "Threads_connected"');
341
+
342
+ return {
343
+ type: 'mysql',
344
+ threadsConnected: rows[0]?.Value || 0
345
+ };
346
+ } catch (error) {
347
+ return { type: 'mysql', error: error.message };
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Get Redis statistics
353
+ */
354
+ async getRedisStats(connection) {
355
+ try {
356
+ const info = await connection.info();
357
+ const lines = info.split('\r\n');
358
+ const stats = {};
359
+
360
+ lines.forEach(line => {
361
+ const [key, value] = line.split(':');
362
+ if (key && value) {
363
+ stats[key] = value;
364
+ }
365
+ });
366
+
367
+ return {
368
+ type: 'redis',
369
+ connectedClients: stats.connected_clients,
370
+ usedMemory: stats.used_memory_human,
371
+ uptime: stats.uptime_in_seconds
372
+ };
373
+ } catch (error) {
374
+ return { type: 'redis', error: error.message };
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Timeout helper
380
+ */
381
+ timeout(ms) {
382
+ return new Promise((_, reject) =>
383
+ setTimeout(() => reject(new Error('Query timeout')), ms)
384
+ );
385
+ }
386
+ }
387
+
388
+ module.exports = DbConnectionMonitor;
389
+