UNPKG

firestore-queue

Version:

A powerful, scalable queue system built on Google Firestore with time-based indexing, auto-configuration, and connection reuse

211 lines (209 loc) โ€ข 7.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueueMonitor = void 0; exports.createMonitoring = createMonitoring; class QueueMonitor { constructor(queue, config = {}) { this.queue = queue; this.config = { metricsIntervalMs: 60000, // 1 minute logLevel: 'info', enableHealthChecks: true, ...config, }; this.setupEventListeners(); } /** * Setup event listeners for queue monitoring */ setupEventListeners() { this.queue.on('message.enqueued', (message) => { this.log('debug', `Message enqueued: ${message.id} (type: ${message.type})`); }); this.queue.on('message.completed', (message, consumerId) => { this.log('debug', `Message completed: ${message.id} by consumer ${consumerId}`); }); this.queue.on('message.failed', (message, error, consumerId) => { this.log('warn', `Message failed: ${message.id} by consumer ${consumerId} - ${error}`); }); this.queue.on('consumer.error', (consumerId, error) => { this.log('error', `Consumer error: ${consumerId} - ${error}`); }); this.queue.on('batch.processed', (consumerId, result) => { this.log('info', `Batch processed by ${consumerId}: ${result.successful}/${result.processed} successful`); }); } /** * Start monitoring */ start() { if (this.metricsTimer) return; this.log('info', 'Starting queue monitoring'); this.metricsTimer = setInterval(async () => { try { await this.collectAndAnalyzeMetrics(); } catch (error) { this.log('error', `Metrics collection failed: ${error}`); } }, this.config.metricsIntervalMs); } /** * Stop monitoring */ stop() { if (this.metricsTimer) { clearInterval(this.metricsTimer); this.metricsTimer = undefined; this.log('info', 'Queue monitoring stopped'); } } /** * Collect and analyze metrics */ async collectAndAnalyzeMetrics() { const metrics = await this.queue.getMetrics(); // Log metrics this.log('info', `Queue metrics: ${metrics.pendingMessages} pending, ${metrics.processingMessages} processing`); // Check alerts await this.checkAlerts(metrics); // Store for comparison this.lastMetrics = metrics; } /** * Check alert conditions */ async checkAlerts(metrics) { const thresholds = this.config.alertThresholds; if (!thresholds) return; const alerts = []; // Check pending messages if (thresholds.maxPendingMessages && metrics.pendingMessages > thresholds.maxPendingMessages) { alerts.push(`High pending messages: ${metrics.pendingMessages} > ${thresholds.maxPendingMessages}`); } // Check failed percentage if (thresholds.maxFailedPercentage) { const failedPercentage = metrics.totalMessages > 0 ? (metrics.failedMessages / metrics.totalMessages) * 100 : 0; if (failedPercentage > thresholds.maxFailedPercentage) { alerts.push(`High failure rate: ${failedPercentage.toFixed(1)}% > ${thresholds.maxFailedPercentage}%`); } } // Check processing time if (thresholds.maxProcessingTimeMs && metrics.averageProcessingTimeMs > thresholds.maxProcessingTimeMs) { alerts.push(`Slow processing: ${metrics.averageProcessingTimeMs}ms > ${thresholds.maxProcessingTimeMs}ms`); } // Send alerts for (const alert of alerts) { await this.sendAlert(alert, metrics); } } /** * Send alert notification */ async sendAlert(message, metrics) { this.log('warn', `ALERT: ${message}`); if (this.config.webhookUrl) { try { const response = await fetch(this.config.webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ alert: message, metrics, timestamp: new Date().toISOString(), }), }); if (!response.ok) { this.log('error', `Failed to send webhook alert: ${response.statusText}`); } } catch (error) { this.log('error', `Webhook alert failed: ${error}`); } } } /** * Get current metrics */ async getCurrentMetrics() { return await this.queue.getMetrics(); } /** * Get health status */ async getHealthStatus() { const metrics = await this.queue.getMetrics(); const issues = []; // Check for unhealthy consumers const unhealthyConsumers = metrics.consumerHealth.filter(consumer => !consumer.isActive || Date.now() - consumer.lastHeartbeat.getTime() > 300000 // 5 minutes ); if (unhealthyConsumers.length > 0) { issues.push(`${unhealthyConsumers.length} unhealthy consumers`); } // Check for stale processing messages if (metrics.processingMessages > metrics.pendingMessages * 0.5) { issues.push('High number of stuck processing messages'); } // Check for no recent activity if (this.lastMetrics) { const noProgress = metrics.completedMessages === this.lastMetrics.completedMessages && metrics.pendingMessages > 0; if (noProgress) { issues.push('No processing progress detected'); } } return { healthy: issues.length === 0, issues, metrics, }; } /** * Generate monitoring report */ generateReport() { if (!this.lastMetrics) return 'No metrics available'; const m = this.lastMetrics; const totalProcessed = m.completedMessages + m.failedMessages; const successRate = totalProcessed > 0 ? ((m.completedMessages / totalProcessed) * 100).toFixed(1) : '0'; return ` ๐Ÿ“Š Fire Queue Monitoring Report ============================== ๐Ÿ“ˆ Messages: ${m.totalMessages} total, ${m.pendingMessages} pending, ${m.processingMessages} processing โœ… Success Rate: ${successRate}% (${m.completedMessages} completed, ${m.failedMessages} failed) โฑ๏ธ Avg Processing Time: ${m.averageProcessingTimeMs.toFixed(0)}ms ๐Ÿงน Expired: ${m.expiredMessages} ๐Ÿ‘ฅ Consumers: ${m.consumerHealth.length} total Active: ${m.consumerHealth.filter(c => c.isActive).length} Inactive: ${m.consumerHealth.filter(c => !c.isActive).length} ๐Ÿ“Š By Type: ${Object.entries(m.messagesByType).map(([type, count]) => `${type}:${count}`).join(', ')} โšก By Priority: ${Object.entries(m.messagesByPriority).map(([pri, count]) => `P${pri}:${count}`).join(', ')} `.trim(); } /** * Log with level checking */ log(level, message) { const levels = { debug: 0, info: 1, warn: 2, error: 3 }; const currentLevel = levels[this.config.logLevel || 'info']; const messageLevel = levels[level]; if (messageLevel >= currentLevel) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`); } } } exports.QueueMonitor = QueueMonitor; /** * Factory function for creating monitoring */ function createMonitoring(queue, config) { return new QueueMonitor(queue, config); } //# sourceMappingURL=monitoring.js.map