mastercontroller 1.3.10 → 1.3.13

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.
@@ -0,0 +1,347 @@
1
+ /**
2
+ * HealthCheck - Production-ready health monitoring endpoint
3
+ * Version: 1.0.0
4
+ *
5
+ * Provides a /_health endpoint for load balancers, orchestrators (Kubernetes, Docker Swarm),
6
+ * and monitoring tools (Datadog, New Relic, etc.)
7
+ *
8
+ * Usage in MasterControl pipeline:
9
+ *
10
+ * const { healthCheck } = require('./monitoring/HealthCheck');
11
+ * master.pipeline.use(healthCheck.middleware());
12
+ *
13
+ * Health check endpoint: GET /_health
14
+ * Returns: { status: 'healthy', uptime, memory, version, timestamp }
15
+ */
16
+
17
+ const os = require('os');
18
+ const { logger } = require('../error/MasterErrorLogger');
19
+
20
+ class HealthCheck {
21
+ constructor(options = {}) {
22
+ this.options = {
23
+ endpoint: options.endpoint || '/_health',
24
+ includeDetails: options.includeDetails !== false,
25
+ customChecks: options.customChecks || [],
26
+ timeout: options.timeout || 5000, // 5 seconds
27
+ ...options
28
+ };
29
+
30
+ this.startTime = Date.now();
31
+ this.version = options.version || require('../package.json').version;
32
+ this.customHealthChecks = [];
33
+ }
34
+
35
+ /**
36
+ * Add custom health check function
37
+ * @param {string} name - Name of the health check
38
+ * @param {Function} checkFn - Async function that returns { healthy: boolean, details?: any }
39
+ */
40
+ addCheck(name, checkFn) {
41
+ if (typeof checkFn !== 'function') {
42
+ throw new Error('Health check must be a function');
43
+ }
44
+ this.customHealthChecks.push({ name, checkFn });
45
+ }
46
+
47
+ /**
48
+ * Middleware for MasterPipeline
49
+ */
50
+ middleware() {
51
+ const self = this;
52
+
53
+ return async (ctx, next) => {
54
+ const requestPath = ctx.request.url.split('?')[0];
55
+
56
+ // Only handle health check endpoint
57
+ if (requestPath !== self.options.endpoint) {
58
+ return await next();
59
+ }
60
+
61
+ // Log health check request
62
+ logger.debug({
63
+ code: 'MC_HEALTH_CHECK',
64
+ message: 'Health check requested',
65
+ ip: ctx.request.connection.remoteAddress
66
+ });
67
+
68
+ try {
69
+ const health = await self.check();
70
+
71
+ // Set response headers
72
+ ctx.response.setHeader('Content-Type', 'application/json');
73
+ ctx.response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
74
+ ctx.response.setHeader('X-Content-Type-Options', 'nosniff');
75
+
76
+ // Set status code based on health
77
+ ctx.response.statusCode = health.status === 'healthy' ? 200 : 503;
78
+
79
+ // Send response
80
+ ctx.response.end(JSON.stringify(health, null, 2));
81
+
82
+ } catch (error) {
83
+ logger.error({
84
+ code: 'MC_HEALTH_CHECK_ERROR',
85
+ message: 'Health check failed',
86
+ error: error.message,
87
+ stack: error.stack
88
+ });
89
+
90
+ // Return unhealthy status
91
+ ctx.response.statusCode = 503;
92
+ ctx.response.setHeader('Content-Type', 'application/json');
93
+ ctx.response.end(JSON.stringify({
94
+ status: 'unhealthy',
95
+ error: error.message,
96
+ timestamp: new Date().toISOString()
97
+ }, null, 2));
98
+ }
99
+
100
+ // Don't call next() - this is a terminal endpoint
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Perform health check
106
+ */
107
+ async check() {
108
+ const startTime = Date.now();
109
+
110
+ try {
111
+ // Basic system metrics
112
+ const uptime = process.uptime();
113
+ const memory = process.memoryUsage();
114
+ const cpuUsage = process.cpuUsage();
115
+
116
+ // System information
117
+ const systemInfo = {
118
+ platform: process.platform,
119
+ nodeVersion: process.version,
120
+ arch: process.arch,
121
+ cpus: os.cpus().length,
122
+ totalMemory: os.totalmem(),
123
+ freeMemory: os.freemem(),
124
+ loadAverage: os.loadavg()
125
+ };
126
+
127
+ // Run custom health checks
128
+ const customChecks = {};
129
+ let allHealthy = true;
130
+
131
+ for (const check of this.customHealthChecks) {
132
+ try {
133
+ const result = await Promise.race([
134
+ check.checkFn(),
135
+ new Promise((_, reject) =>
136
+ setTimeout(() => reject(new Error('Health check timeout')), this.options.timeout)
137
+ )
138
+ ]);
139
+
140
+ customChecks[check.name] = result;
141
+
142
+ if (result.healthy === false) {
143
+ allHealthy = false;
144
+ }
145
+ } catch (error) {
146
+ customChecks[check.name] = {
147
+ healthy: false,
148
+ error: error.message
149
+ };
150
+ allHealthy = false;
151
+ }
152
+ }
153
+
154
+ // Memory health check (fail if >90% memory used)
155
+ const memoryUsagePercent = (memory.heapUsed / memory.heapTotal) * 100;
156
+ const memoryHealthy = memoryUsagePercent < 90;
157
+
158
+ if (!memoryHealthy) {
159
+ allHealthy = false;
160
+ logger.warn({
161
+ code: 'MC_HEALTH_MEMORY_HIGH',
162
+ message: 'Memory usage critically high',
163
+ memoryUsagePercent: memoryUsagePercent.toFixed(2),
164
+ heapUsed: memory.heapUsed,
165
+ heapTotal: memory.heapTotal
166
+ });
167
+ }
168
+
169
+ // Build response
170
+ const response = {
171
+ status: allHealthy && memoryHealthy ? 'healthy' : 'degraded',
172
+ uptime: Math.floor(uptime),
173
+ version: this.version,
174
+ timestamp: new Date().toISOString(),
175
+ responseTime: Date.now() - startTime
176
+ };
177
+
178
+ // Add detailed metrics if enabled
179
+ if (this.options.includeDetails) {
180
+ response.memory = {
181
+ heapUsed: memory.heapUsed,
182
+ heapTotal: memory.heapTotal,
183
+ rss: memory.rss,
184
+ external: memory.external,
185
+ usagePercent: memoryUsagePercent.toFixed(2)
186
+ };
187
+
188
+ response.cpu = {
189
+ user: cpuUsage.user,
190
+ system: cpuUsage.system
191
+ };
192
+
193
+ response.system = systemInfo;
194
+
195
+ if (Object.keys(customChecks).length > 0) {
196
+ response.checks = customChecks;
197
+ }
198
+ }
199
+
200
+ return response;
201
+
202
+ } catch (error) {
203
+ logger.error({
204
+ code: 'MC_HEALTH_CHECK_FAILURE',
205
+ message: 'Health check encountered error',
206
+ error: error.message,
207
+ stack: error.stack
208
+ });
209
+
210
+ return {
211
+ status: 'unhealthy',
212
+ error: error.message,
213
+ timestamp: new Date().toISOString(),
214
+ responseTime: Date.now() - startTime
215
+ };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Express/Connect compatible middleware (for compatibility)
221
+ */
222
+ expressMiddleware() {
223
+ const self = this;
224
+
225
+ return async (req, res, next) => {
226
+ const requestPath = req.url.split('?')[0];
227
+
228
+ if (requestPath !== self.options.endpoint) {
229
+ return next();
230
+ }
231
+
232
+ try {
233
+ const health = await self.check();
234
+
235
+ res.setHeader('Content-Type', 'application/json');
236
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
237
+ res.statusCode = health.status === 'healthy' ? 200 : 503;
238
+ res.end(JSON.stringify(health, null, 2));
239
+
240
+ } catch (error) {
241
+ res.statusCode = 503;
242
+ res.setHeader('Content-Type', 'application/json');
243
+ res.end(JSON.stringify({
244
+ status: 'unhealthy',
245
+ error: error.message,
246
+ timestamp: new Date().toISOString()
247
+ }, null, 2));
248
+ }
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Get uptime in seconds
254
+ */
255
+ getUptime() {
256
+ return Math.floor(process.uptime());
257
+ }
258
+
259
+ /**
260
+ * Get memory stats
261
+ */
262
+ getMemory() {
263
+ return process.memoryUsage();
264
+ }
265
+
266
+ /**
267
+ * Manual health check (for internal use)
268
+ */
269
+ async isHealthy() {
270
+ const result = await this.check();
271
+ return result.status === 'healthy';
272
+ }
273
+ }
274
+
275
+ // Singleton instance
276
+ const healthCheck = new HealthCheck({
277
+ endpoint: '/_health',
278
+ includeDetails: process.env.NODE_ENV !== 'production' // Hide details in production for security
279
+ });
280
+
281
+ /**
282
+ * Example custom health checks
283
+ */
284
+
285
+ // Database health check example
286
+ function createDatabaseCheck(db) {
287
+ return async () => {
288
+ try {
289
+ // Example: Check database connectivity
290
+ await db.ping(); // or db.query('SELECT 1')
291
+ return { healthy: true, details: { connected: true } };
292
+ } catch (error) {
293
+ return { healthy: false, error: error.message };
294
+ }
295
+ };
296
+ }
297
+
298
+ // Redis health check example
299
+ function createRedisCheck(redis) {
300
+ return async () => {
301
+ try {
302
+ await redis.ping();
303
+ return { healthy: true, details: { connected: true } };
304
+ } catch (error) {
305
+ return { healthy: false, error: error.message };
306
+ }
307
+ };
308
+ }
309
+
310
+ // API dependency health check example
311
+ function createAPIHealthCheck(apiUrl) {
312
+ return async () => {
313
+ try {
314
+ const https = require('https');
315
+ const http = require('http');
316
+
317
+ return new Promise((resolve) => {
318
+ const client = apiUrl.startsWith('https') ? https : http;
319
+ const req = client.get(apiUrl, (res) => {
320
+ resolve({
321
+ healthy: res.statusCode >= 200 && res.statusCode < 300,
322
+ details: { statusCode: res.statusCode }
323
+ });
324
+ });
325
+
326
+ req.on('error', (error) => {
327
+ resolve({ healthy: false, error: error.message });
328
+ });
329
+
330
+ req.setTimeout(3000, () => {
331
+ req.destroy();
332
+ resolve({ healthy: false, error: 'Timeout' });
333
+ });
334
+ });
335
+ } catch (error) {
336
+ return { healthy: false, error: error.message };
337
+ }
338
+ };
339
+ }
340
+
341
+ module.exports = {
342
+ HealthCheck,
343
+ healthCheck,
344
+ createDatabaseCheck,
345
+ createRedisCheck,
346
+ createAPIHealthCheck
347
+ };