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
JavaScript
;
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