UNPKG

@mraicodedev/bsc-token-detector

Version:

🚀 Real-time BSC token detector with advanced filtering, health monitoring, and comprehensive event system. Detect new tokens on Binance Smart Chain as they are created!

741 lines (643 loc) • 19.2 kB
/** * Health Monitoring System for BSC Token Detector * Provides comprehensive health checks and status reporting for all components */ import { EventEmitter } from 'events'; import { Logger } from './Logger.js'; /** * Health status levels */ export const HealthStatus = { HEALTHY: 'healthy', DEGRADED: 'degraded', UNHEALTHY: 'unhealthy', CRITICAL: 'critical', UNKNOWN: 'unknown' }; /** * Health check result */ export class HealthCheckResult { constructor(name, status, message = '', details = {}, duration = 0) { this.name = name; this.status = status; this.message = message; this.details = details; this.duration = duration; this.timestamp = new Date().toISOString(); } /** * Check if health check passed */ isHealthy() { return this.status === HealthStatus.HEALTHY; } /** * Check if health check indicates degraded performance */ isDegraded() { return this.status === HealthStatus.DEGRADED; } /** * Check if health check failed */ isUnhealthy() { return [HealthStatus.UNHEALTHY, HealthStatus.CRITICAL].includes(this.status); } /** * Convert to JSON */ toJSON() { return { name: this.name, status: this.status, message: this.message, details: this.details, duration: this.duration, timestamp: this.timestamp }; } } /** * Individual health check implementation */ export class HealthCheck { constructor(name, checkFunction, config = {}) { this.name = name; this.checkFunction = checkFunction; this.config = { timeout: 5000, interval: 30000, retries: 1, enabled: true, critical: false, ...config }; this.lastResult = null; this.history = []; this.maxHistorySize = 100; // Statistics this.stats = { totalChecks: 0, successfulChecks: 0, failedChecks: 0, averageDuration: 0, totalDuration: 0, lastCheck: null, consecutiveFailures: 0, consecutiveSuccesses: 0 }; } /** * Execute health check with timeout and retry logic */ async execute() { if (!this.config.enabled) { return new HealthCheckResult( this.name, HealthStatus.UNKNOWN, 'Health check disabled' ); } const startTime = Date.now(); let lastError; for (let attempt = 1; attempt <= this.config.retries + 1; attempt++) { try { // Execute with timeout const result = await Promise.race([ this.checkFunction(), this.createTimeoutPromise() ]); const duration = Date.now() - startTime; const healthResult = this.processResult(result, duration); this.updateStats(true, duration); this.addToHistory(healthResult); return healthResult; } catch (error) { lastError = error; if (attempt <= this.config.retries) { // Wait before retry await this.sleep(1000 * attempt); } } } // All attempts failed const duration = Date.now() - startTime; const healthResult = new HealthCheckResult( this.name, HealthStatus.UNHEALTHY, `Health check failed: ${lastError.message}`, { error: lastError.message, attempts: this.config.retries + 1 }, duration ); this.updateStats(false, duration); this.addToHistory(healthResult); return healthResult; } /** * Process health check result */ processResult(result, duration) { // If result is already a HealthCheckResult, return it if (result instanceof HealthCheckResult) { result.duration = duration; return result; } // If result is boolean if (typeof result === 'boolean') { return new HealthCheckResult( this.name, result ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY, result ? 'Health check passed' : 'Health check failed', {}, duration ); } // If result is object with status if (result && typeof result === 'object') { return new HealthCheckResult( this.name, result.status || HealthStatus.HEALTHY, result.message || 'Health check completed', result.details || {}, duration ); } // Default to healthy return new HealthCheckResult( this.name, HealthStatus.HEALTHY, 'Health check passed', { result }, duration ); } /** * Create timeout promise */ createTimeoutPromise() { return new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Health check timeout after ${this.config.timeout}ms`)); }, this.config.timeout); }); } /** * Update statistics */ updateStats(success, duration) { this.stats.totalChecks++; this.stats.totalDuration += duration; this.stats.averageDuration = this.stats.totalDuration / this.stats.totalChecks; this.stats.lastCheck = new Date().toISOString(); if (success) { this.stats.successfulChecks++; this.stats.consecutiveSuccesses++; this.stats.consecutiveFailures = 0; } else { this.stats.failedChecks++; this.stats.consecutiveFailures++; this.stats.consecutiveSuccesses = 0; } } /** * Add result to history */ addToHistory(result) { this.lastResult = result; this.history.push(result); // Maintain history size if (this.history.length > this.maxHistorySize) { this.history.shift(); } } /** * Get health check statistics */ getStats() { return { ...this.stats, successRate: this.stats.totalChecks > 0 ? (this.stats.successfulChecks / this.stats.totalChecks) * 100 : 0, config: this.config, lastResult: this.lastResult?.toJSON() }; } /** * Get recent history */ getHistory(limit = 10) { return this.history.slice(-limit).map(result => result.toJSON()); } /** * Sleep utility */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * Main health monitoring system */ export class HealthMonitor extends EventEmitter { constructor(config = {}) { super(); this.config = { checkInterval: 30000, // 30 seconds enableAutoCheck: true, enableMetrics: true, maxHistorySize: 1000, alertThresholds: { consecutiveFailures: 3, failureRate: 50, // percentage responseTime: 5000 // milliseconds }, ...config }; this.logger = new Logger().child('HealthMonitor'); this.healthChecks = new Map(); this.isRunning = false; this.checkTimer = null; // Overall system health this.systemHealth = { status: HealthStatus.UNKNOWN, message: 'System not initialized', lastCheck: null, components: {}, metrics: { totalComponents: 0, healthyComponents: 0, degradedComponents: 0, unhealthyComponents: 0, criticalComponents: 0 } }; // Metrics collection this.metrics = { totalChecks: 0, totalDuration: 0, averageResponseTime: 0, checksPerMinute: 0, lastMinuteChecks: [], alerts: [] }; } /** * Register a health check */ registerHealthCheck(name, checkFunction, config = {}) { const healthCheck = new HealthCheck(name, checkFunction, config); this.healthChecks.set(name, healthCheck); this.logger.info(`Registered health check: ${name}`, { timeout: healthCheck.config.timeout, interval: healthCheck.config.interval, critical: healthCheck.config.critical }); return healthCheck; } /** * Unregister a health check */ unregisterHealthCheck(name) { if (this.healthChecks.delete(name)) { this.logger.info(`Unregistered health check: ${name}`); return true; } return false; } /** * Start health monitoring */ start() { if (this.isRunning) { this.logger.warn('Health monitor is already running'); return; } this.isRunning = true; this.logger.info('Starting health monitor', { checkInterval: this.config.checkInterval, registeredChecks: this.healthChecks.size }); // Perform initial health check this.performHealthChecks().catch(error => { this.logger.error('Initial health check failed:', error); }); // Start periodic checks if enabled if (this.config.enableAutoCheck) { this.startPeriodicChecks(); } this.emit('started'); } /** * Stop health monitoring */ stop() { if (!this.isRunning) { return; } this.isRunning = false; if (this.checkTimer) { clearInterval(this.checkTimer); this.checkTimer = null; } this.logger.info('Health monitor stopped'); this.emit('stopped'); } /** * Start periodic health checks */ startPeriodicChecks() { this.checkTimer = setInterval(async () => { try { await this.performHealthChecks(); } catch (error) { this.logger.error('Periodic health check failed:', error); } }, this.config.checkInterval); } /** * Perform all health checks */ async performHealthChecks() { if (this.healthChecks.size === 0) { this.updateSystemHealth(HealthStatus.UNKNOWN, 'No health checks registered'); return this.systemHealth; } const startTime = Date.now(); const results = new Map(); // Execute all health checks concurrently const checkPromises = Array.from(this.healthChecks.entries()).map(async ([name, healthCheck]) => { try { const result = await healthCheck.execute(); results.set(name, result); return { name, result }; } catch (error) { const errorResult = new HealthCheckResult( name, HealthStatus.CRITICAL, `Health check execution failed: ${error.message}`, { error: error.message } ); results.set(name, errorResult); return { name, result: errorResult }; } }); await Promise.allSettled(checkPromises); // Update metrics const totalDuration = Date.now() - startTime; this.updateMetrics(results.size, totalDuration); // Analyze results and update system health this.analyzeHealthResults(results); // Check for alerts this.checkAlerts(results); // Emit health check completed event this.emit('healthCheckCompleted', { systemHealth: this.systemHealth, results: Array.from(results.values()).map(r => r.toJSON()), duration: totalDuration }); return this.systemHealth; } /** * Perform health check for specific component */ async performHealthCheck(name) { const healthCheck = this.healthChecks.get(name); if (!healthCheck) { throw new Error(`Health check '${name}' not found`); } const result = await healthCheck.execute(); // Update system health if this was a critical component if (healthCheck.config.critical) { await this.performHealthChecks(); } return result; } /** * Analyze health check results and update system health */ analyzeHealthResults(results) { const metrics = { totalComponents: results.size, healthyComponents: 0, degradedComponents: 0, unhealthyComponents: 0, criticalComponents: 0 }; const componentResults = {}; let overallStatus = HealthStatus.HEALTHY; let statusMessage = 'All components healthy'; // Analyze each result for (const [name, result] of results) { componentResults[name] = result.toJSON(); switch (result.status) { case HealthStatus.HEALTHY: metrics.healthyComponents++; break; case HealthStatus.DEGRADED: metrics.degradedComponents++; if (overallStatus === HealthStatus.HEALTHY) { overallStatus = HealthStatus.DEGRADED; statusMessage = 'Some components degraded'; } break; case HealthStatus.UNHEALTHY: metrics.unhealthyComponents++; if ([HealthStatus.HEALTHY, HealthStatus.DEGRADED].includes(overallStatus)) { overallStatus = HealthStatus.UNHEALTHY; statusMessage = 'Some components unhealthy'; } break; case HealthStatus.CRITICAL: metrics.criticalComponents++; overallStatus = HealthStatus.CRITICAL; statusMessage = 'Critical components failing'; break; } } // Check for critical component failures const criticalFailures = Array.from(this.healthChecks.entries()) .filter(([name, check]) => check.config.critical && results.get(name)?.isUnhealthy()) .map(([name]) => name); if (criticalFailures.length > 0) { overallStatus = HealthStatus.CRITICAL; statusMessage = `Critical components failing: ${criticalFailures.join(', ')}`; } this.updateSystemHealth(overallStatus, statusMessage, componentResults, metrics); } /** * Update system health status */ updateSystemHealth(status, message, components = {}, metrics = null) { const previousStatus = this.systemHealth.status; this.systemHealth = { status, message, lastCheck: new Date().toISOString(), components, metrics: metrics || this.systemHealth.metrics }; // Emit status change event if status changed if (previousStatus !== status) { this.logger.info(`System health status changed: ${previousStatus} -> ${status}`, { message, components: Object.keys(components).length }); this.emit('statusChanged', { previousStatus, currentStatus: status, message, systemHealth: this.systemHealth }); } } /** * Update metrics */ updateMetrics(checkCount, duration) { if (!this.config.enableMetrics) { return; } this.metrics.totalChecks += checkCount; this.metrics.totalDuration += duration; this.metrics.averageResponseTime = this.metrics.totalDuration / this.metrics.totalChecks; // Track checks per minute const now = Date.now(); this.metrics.lastMinuteChecks.push(now); // Remove checks older than 1 minute this.metrics.lastMinuteChecks = this.metrics.lastMinuteChecks.filter( timestamp => now - timestamp < 60000 ); this.metrics.checksPerMinute = this.metrics.lastMinuteChecks.length; } /** * Check for alert conditions */ checkAlerts(results) { const alerts = []; for (const [name, result] of results) { const healthCheck = this.healthChecks.get(name); const stats = healthCheck.getStats(); // Check consecutive failures if (stats.consecutiveFailures >= this.config.alertThresholds.consecutiveFailures) { alerts.push({ type: 'consecutive_failures', component: name, message: `${name} has ${stats.consecutiveFailures} consecutive failures`, severity: 'high', timestamp: new Date().toISOString() }); } // Check failure rate if (stats.successRate < (100 - this.config.alertThresholds.failureRate)) { alerts.push({ type: 'high_failure_rate', component: name, message: `${name} has ${stats.successRate.toFixed(1)}% success rate`, severity: 'medium', timestamp: new Date().toISOString() }); } // Check response time if (result.duration > this.config.alertThresholds.responseTime) { alerts.push({ type: 'slow_response', component: name, message: `${name} response time ${result.duration}ms exceeds threshold`, severity: 'low', timestamp: new Date().toISOString() }); } } // Emit alerts for (const alert of alerts) { this.emit('alert', alert); this.logger.warn(`Health alert: ${alert.message}`, alert); } // Store alerts in metrics this.metrics.alerts.push(...alerts); // Maintain alert history size if (this.metrics.alerts.length > this.config.maxHistorySize) { this.metrics.alerts = this.metrics.alerts.slice(-this.config.maxHistorySize); } } /** * Get system health status */ getSystemHealth() { return this.systemHealth; } /** * Get health check statistics */ getHealthCheckStats(name) { const healthCheck = this.healthChecks.get(name); return healthCheck ? healthCheck.getStats() : null; } /** * Get all health check statistics */ getAllHealthCheckStats() { const stats = {}; for (const [name, healthCheck] of this.healthChecks) { stats[name] = healthCheck.getStats(); } return stats; } /** * Get monitoring metrics */ getMetrics() { return { ...this.metrics, systemHealth: this.systemHealth, registeredChecks: this.healthChecks.size, isRunning: this.isRunning }; } /** * Get recent alerts */ getRecentAlerts(limit = 50) { return this.metrics.alerts.slice(-limit); } /** * Clear alert history */ clearAlerts() { this.metrics.alerts = []; this.logger.info('Alert history cleared'); } /** * Enable/disable health check */ setHealthCheckEnabled(name, enabled) { const healthCheck = this.healthChecks.get(name); if (healthCheck) { healthCheck.config.enabled = enabled; this.logger.info(`Health check '${name}' ${enabled ? 'enabled' : 'disabled'}`); return true; } return false; } /** * Update health check configuration */ updateHealthCheckConfig(name, config) { const healthCheck = this.healthChecks.get(name); if (healthCheck) { healthCheck.config = { ...healthCheck.config, ...config }; this.logger.info(`Health check '${name}' configuration updated`, config); return true; } return false; } /** * Cleanup resources */ destroy() { this.stop(); this.healthChecks.clear(); this.removeAllListeners(); } }