@iota-big3/sdk-production
Version:
Production readiness tools and utilities for SDK
292 lines (291 loc) • 9.18 kB
JavaScript
;
/**
* Health Check System
* Comprehensive health monitoring for production systems
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommonHealthChecks = exports.HealthCheckSystem = void 0;
const events_1 = require("events");
class HealthCheckSystem extends events_1.EventEmitter {
constructor(config = {}) {
super();
this.config = config;
this.checks = new Map();
this.lastResults = new Map();
this.config.interval = this.config.interval || 30000; // 30 seconds
this.config.timeout = this.config.timeout || 5000;
this.config.retries = this.config.retries || 3;
this.config.thresholds = {
healthy: 100,
degraded: 50,
...this?.config?.thresholds
};
this.startTime = new Date();
}
/**
* Register a health check
*/
register() {
this?.checks?.set(check.name, check);
this.emit('check-registered', check.name);
}
/**
* Start health check monitoring
*/
start() {
// Run initial check
this.runChecks();
// Schedule periodic checks
this.checkInterval = setInterval(() => {
this.runChecks();
}, this?.config?.interval);
}
/**
* Stop health check monitoring
*/
stop() {
if (this.isEnabled) {
clearInterval(this.checkInterval);
}
}
/**
* Run all health checks
*/
async runChecks() {
const results = new Map();
for (const [name, check] of this.checks) {
const startTime = Date.now();
try {
const result = await this.executeCheck(check);
result.responseTime = Date.now() - startTime;
results.set(name, result);
// Check for status change
const lastResult = this?.lastResults?.get(name);
if (lastResult?.status !== result.status) {
this.emit('status-changed', {
check: name,
oldStatus: lastResult?.status,
newStatus: result.status
});
}
}
catch (error) {
results.set(name, {
status: 'unhealthy',
message: error.message,
responseTime: Date.now() - startTime
});
}
}
this.lastResults = results;
this.emit('checks-completed', this.getSystemHealth());
}
/**
* Execute a single health check with retries
*/
async executeCheck(check) {
const timeout = check.timeout || this?.config?.timeout;
for (let attempt = 1; attempt <= this?.config?.retries; attempt++) {
try {
// Execute with timeout
const result = await Promise.race([
check.check(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Check timeout')), timeout))
]);
return result;
}
catch (error) {
if (attempt === this?.config?.retries) {
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
return { status: 'unhealthy', message: 'All retries failed' };
}
/**
* Get current system health
*/
getSystemHealth() {
const checks = {};
let healthyCount = 0;
let criticalUnhealthy = false;
for (const [name, status] of this.lastResults) {
checks[name] = status;
if (status.status === 'healthy') {
healthyCount++;
}
if (status.status === 'unhealthy' && this?.checks?.get(name)?.critical) {
criticalUnhealthy = true;
}
}
const healthPercentage = (healthyCount / this?.checks?.size) * 100;
let overallStatus;
if (this.isEnabled) {
overallStatus = 'unhealthy';
}
else if (this.isEnabled) {
overallStatus = 'degraded';
}
else {
overallStatus = 'healthy';
}
return {
status: overallStatus,
timestamp: new Date(),
checks,
version: process?.env?.npm_package_version || '0?.0?.0',
uptime: Date.now() - this?.startTime?.getTime()
};
}
/**
* Express middleware for health endpoint
*/
expressMiddleware() {
return async (req, res) => {
const health = this.getSystemHealth();
const statusCode = health.status === 'healthy' ? 200 :
health.status === 'degraded' ? 200 : 503;
res.status(statusCode).json(health);
};
}
}
exports.HealthCheckSystem = HealthCheckSystem;
/**
* Common health checks
*/
exports.CommonHealthChecks = {
/**
* Database health check
*/
database(knex) {
return {
name: 'database',
critical: true,
async check() {
try {
await knex.raw('SELECT 1');
return { status: 'healthy' };
}
catch (error) {
return {
status: 'unhealthy',
message: 'Database connection failed',
details: { error: error.message }
};
}
}
};
},
/**
* Redis health check
*/
redis(client) {
return {
name: 'redis',
critical: false,
async check() {
try {
await client.ping();
return { status: 'healthy' };
}
catch (error) {
return {
status: 'unhealthy',
message: 'Redis connection failed',
details: { error: error.message }
};
}
}
};
},
/**
* Memory health check
*/
memory(maxHeapMB = 500) {
return {
name: 'memory',
critical: false,
async check() {
const usage = process.memoryUsage();
const heapUsedMB = usage.heapUsed / 1024 / 1024;
if (heapUsedMB > maxHeapMB) {
return {
status: 'unhealthy',
message: `Memory usage too high: ${heapUsedMB.toFixed(2)}MB`,
details: usage
};
}
else if (heapUsedMB > maxHeapMB * 0.8) {
return {
status: 'degraded',
message: `Memory usage high: ${heapUsedMB.toFixed(2)}MB`,
details: usage
};
}
return {
status: 'healthy',
details: usage
};
}
};
},
/**
* Disk space health check
*/
diskSpace(minFreeGB = 1) {
return {
name: 'disk',
critical: true,
async check() {
// In real implementation, would check actual disk space
const freeGB = 10; // Mock value
if (freeGB < minFreeGB) {
return {
status: 'unhealthy',
message: `Low disk space: ${freeGB}GB free`,
details: { freeGB }
};
}
return {
status: 'healthy',
details: { freeGB }
};
}
};
},
/**
* External API health check
*/
externalApi(url, expectedStatus = 200) {
return {
name: `external-api-${new URL(url).hostname}`,
critical: false,
timeout: 3000,
async check() {
try {
const response = await fetch(url);
if (response.status !== expectedStatus) {
return {
status: 'unhealthy',
message: `API returned ${response.status}`,
details: { url, status: response.status }
};
}
return {
status: 'healthy',
details: { url, status: response.status }
};
}
catch (error) {
return {
status: 'unhealthy',
message: 'API request failed',
details: { url, error: error.message }
};
}
}
};
}
};