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.
- package/README.md +93 -0
- package/node-monitor/ARCHITECTURE.md +341 -0
- package/node-monitor/CHANGELOG.md +105 -0
- package/node-monitor/CONTRIBUTING.md +96 -0
- package/node-monitor/DESIGN_IMPROVEMENTS.md +286 -0
- package/node-monitor/FILTER_BUTTONS_FIX.md +303 -0
- package/node-monitor/GETTING_STARTED.md +416 -0
- package/node-monitor/INSTALLATION.md +470 -0
- package/node-monitor/LICENSE +22 -0
- package/node-monitor/PUBLISHING_GUIDE.md +331 -0
- package/node-monitor/QUICK_REFERENCE.md +252 -0
- package/node-monitor/README.md +458 -0
- package/node-monitor/READY_TO_PUBLISH.md +272 -0
- package/node-monitor/SETUP_GUIDE.md +479 -0
- package/node-monitor/examples/EMAIL_SETUP_GUIDE.md +282 -0
- package/node-monitor/examples/ERROR_LOGGING_GUIDE.md +405 -0
- package/node-monitor/examples/GET_APP_PASSWORD.md +145 -0
- package/node-monitor/examples/LOG_FILES_REFERENCE.md +336 -0
- package/node-monitor/examples/QUICK_START_EMAIL.md +126 -0
- package/node-monitor/examples/express-app.js +499 -0
- package/node-monitor/examples/package-lock.json +1295 -0
- package/node-monitor/examples/package.json +18 -0
- package/node-monitor/examples/public/css/style.css +718 -0
- package/node-monitor/examples/public/js/dashboard.js +207 -0
- package/node-monitor/examples/public/js/health.js +114 -0
- package/node-monitor/examples/public/js/main.js +89 -0
- package/node-monitor/examples/public/js/metrics.js +225 -0
- package/node-monitor/examples/public/js/theme.js +138 -0
- package/node-monitor/examples/views/dashboard.ejs +20 -0
- package/node-monitor/examples/views/error-logs.ejs +1129 -0
- package/node-monitor/examples/views/health.ejs +21 -0
- package/node-monitor/examples/views/home.ejs +341 -0
- package/node-monitor/examples/views/layout.ejs +50 -0
- package/node-monitor/examples/views/metrics.ejs +16 -0
- package/node-monitor/examples/views/partials/footer.ejs +16 -0
- package/node-monitor/examples/views/partials/header.ejs +35 -0
- package/node-monitor/examples/views/partials/nav.ejs +23 -0
- package/node-monitor/examples/views/status.ejs +390 -0
- package/node-monitor/package-lock.json +4300 -0
- package/node-monitor/package.json +76 -0
- package/node-monitor/pre-publish-check.js +200 -0
- package/node-monitor/src/config/monitoringConfig.js +255 -0
- package/node-monitor/src/index.js +300 -0
- package/node-monitor/src/logger/errorLogger.js +297 -0
- package/node-monitor/src/monitors/apiErrorMonitor.js +156 -0
- package/node-monitor/src/monitors/dbConnectionMonitor.js +389 -0
- package/node-monitor/src/monitors/serverHealthMonitor.js +320 -0
- package/node-monitor/src/monitors/systemResourceMonitor.js +357 -0
- package/node-monitor/src/notifiers/emailNotifier.js +248 -0
- package/node-monitor/src/notifiers/notificationManager.js +96 -0
- package/node-monitor/src/notifiers/slackNotifier.js +209 -0
- package/node-monitor/src/views/dashboard.html +530 -0
- package/node-monitor/src/views/health.html +399 -0
- package/node-monitor/src/views/metrics.html +406 -0
- 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
|
+
|