@iota-big3/sdk-gateway
Version:
Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching
279 lines • 8.84 kB
JavaScript
;
/**
* @iota-big3/sdk-gateway
* Health Checker - Service health monitoring with retry and timeout support
* Phase 2g Implementation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HealthChecker = void 0;
const tslib_1 = require("tslib");
const axios_1 = tslib_1.__importDefault(require("axios"));
const events_1 = require("events");
/**
* Health checker implementation with periodic monitoring
*/
class HealthChecker extends events_1.EventEmitter {
constructor(config, httpClient) {
super();
this.services = new Map();
this.running = false;
this.config = {
interval: config.interval,
timeout: config.timeout,
retries: config.retries ?? 2,
endpoints: config.endpoints || []
};
// Use provided client or create default
this.httpClient = httpClient || axios_1.default.create({
timeout: this.config.timeout,
validateStatus: () => true // Don't throw on any status
});
// Register default endpoint checks
this.config.endpoints.forEach(endpoint => {
this.addEndpointCheck(endpoint);
});
}
/**
* Add a custom health check function
*/
addCheck(serviceId, checkFunction) {
this.services.set(serviceId, {
consecutiveFailures: 0,
checkFunction
});
this.emit('check:added', { serviceId });
}
/**
* Add health check for an HTTP endpoint
*/
addEndpointCheck(endpoint) {
const checkFunction = async () => {
const startTime = Date.now();
try {
const response = await this.httpClient.request({
method: endpoint.method || 'GET',
url: `${endpoint.url}${endpoint.healthPath || '/health'}`,
headers: endpoint.headers
});
const responseTime = Date.now() - startTime;
const expectedStatus = endpoint.expectedStatus || 200;
if (response.status === expectedStatus) {
return {
status: 'healthy',
responseTime,
details: {
statusCode: response.status,
data: response.data
}
};
}
else {
return {
status: 'unhealthy',
responseTime,
error: `Unexpected status: ${response.status}`,
details: {
statusCode: response.status,
expectedStatus
}
};
}
}
catch (error) {
return {
status: 'unhealthy',
responseTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
};
this.addCheck(endpoint.id, checkFunction);
}
/**
* Check health of a specific service
*/
async checkHealthAsync(serviceId) {
const state = this.services.get(serviceId);
if (!state) {
return {
status: 'unhealthy',
error: `No health check registered for service: ${serviceId}`
};
}
// Perform check with retries
let lastError;
let attempts = 0;
while (attempts <= this.config.retries) {
try {
const result = await this.executeCheckAsync(state.checkFunction);
// Update state
const previousStatus = state.lastStatus;
state.lastStatus = result.status;
state.lastCheck = new Date();
state.consecutiveFailures = result.status === 'unhealthy' ? state.consecutiveFailures + 1 : 0;
// Emit status change event
if (previousStatus && previousStatus !== result.status) {
this.emit('health:status:changed', {
serviceId,
previousStatus,
currentStatus: result.status
});
}
return {
...result,
lastChecked: state.lastCheck
};
}
catch (error) {
lastError = error instanceof Error ? error.message : 'Unknown error';
attempts++;
if (attempts <= this.config.retries) {
// Wait before retry (exponential backoff)
await new Promise(resolve => setTimeout(resolve, 100 * Math.pow(2, attempts)));
}
}
}
// All retries failed
state.consecutiveFailures++;
state.lastStatus = 'unhealthy';
state.lastCheck = new Date();
return {
status: 'unhealthy',
error: lastError || 'Max retries exceeded',
lastChecked: state.lastCheck
};
}
/**
* Execute a single health check with timeout
*/
async executeCheckAsync(checkFunction) {
return Promise.race([
checkFunction(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), this.config.timeout))
]);
}
/**
* Check health of all registered services
*/
async checkAllAsync() {
const results = {};
// Check all services in parallel
const checks = Array.from(this.services.keys()).map(async (serviceId) => {
results[serviceId] = await this.checkHealthAsync(serviceId);
});
await Promise.all(checks);
return results;
}
/**
* Get overall health status
*/
async getOverallHealthAsync() {
const results = await this.checkAllAsync();
let healthy = 0;
let unhealthy = 0;
const services = [];
for (const [serviceId, result] of Object.entries(results)) {
if (result.status === 'healthy') {
healthy++;
}
else {
unhealthy++;
}
services.push({
id: serviceId,
status: result.status,
lastChecked: result.lastChecked,
error: result.error
});
}
// Determine overall status
let status;
if (unhealthy === 0) {
status = 'healthy';
}
else if (healthy === 0) {
status = 'unhealthy';
}
else {
status = 'degraded';
}
return {
status,
healthy,
unhealthy,
services
};
}
/**
* Start periodic health checks
*/
async startAsync() {
if (this.running) {
return;
}
this.running = true;
// Initial check
await this.checkAllAsync();
// Start periodic checks
this.intervalId = setInterval(async () => {
try {
await this.checkAllAsync();
this.emit('health:check:completed');
}
catch (error) {
this.emit('health:check:error', error);
}
}, this.config.interval);
this.emit('health:checker:started');
}
/**
* Stop periodic health checks
*/
stop() {
if (!this.running) {
return;
}
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
this.running = false;
this.emit('health:checker:stopped');
}
/**
* Check if health checker is running
*/
isRunning() {
return this.running;
}
/**
* Remove a health check
*/
removeCheck(serviceId) {
this.services.delete(serviceId);
this.emit('check:removed', { serviceId });
}
/**
* Clear all health checks
*/
clear() {
this.services.clear();
this.emit('checks:cleared');
}
/**
* Get current health states
*/
getHealthStates() {
const states = {};
this.services.forEach((state, serviceId) => {
states[serviceId] = {
status: state.lastStatus,
lastCheck: state.lastCheck,
consecutiveFailures: state.consecutiveFailures
};
});
return states;
}
}
exports.HealthChecker = HealthChecker;
//# sourceMappingURL=health-checker.js.map