auto-logmonitor
Version:
A robust, production-ready CLI for log monitoring with API/Kafka output, SMTP email alerts, disk-based queue with optional compression, dead-letter queue, metrics, and full config via file or environment variables. Recent improvements: SMTP alerting, disk
177 lines (154 loc) • 5.86 kB
JavaScript
const { Kafka } = require('kafkajs');
const { loadConfig } = require('./config');
const { logSuccess, logFailure, logRetry, updateQueueSize } = require('./metrics');
class KafkaProducer {
constructor() {
this.config = null;
this.kafka = null;
this.producer = null;
this.isConnected = false;
this.pendingMessages = new Map();
this.retryCounts = new Map();
this.healthy = true;
this.messageId = 0;
}
async initialize() {
this.config = await loadConfig();
this.kafka = new Kafka({
clientId: this.config.kafkaClientId || 'auto-logmonitor',
brokers: this.config.kafkaBrokers,
retry: {
initialRetryTime: 100,
retries: 8
},
connectionTimeout: 3000,
requestTimeout: 30000
});
this.producer = this.kafka.producer({
allowAutoTopicCreation: true,
transactionTimeout: 30000,
maxInFlightRequests: 5,
idempotent: true
});
try {
await this.producer.connect();
this.isConnected = true;
console.log('[KAFKA] Producer connected successfully');
this.startHealthMonitor();
} catch (error) {
console.error('[KAFKA] Failed to connect producer:', error);
throw error;
}
}
async disconnect() {
if (this.isConnected) {
try {
await this.producer.disconnect();
this.isConnected = false;
console.log('[KAFKA] Producer disconnected');
} catch (error) {
console.error('[KAFKA] Error disconnecting producer:', error);
}
}
}
startHealthMonitor() {
setInterval(() => {
const wasHealthy = this.healthy;
this.healthy = this.isConnected && this.pendingMessages.size < this.config.kafkaMaxPendingMessages;
if (!wasHealthy && this.healthy) {
console.log('[KAFKA] Health restored - resuming normal operations');
} else if (wasHealthy && !this.healthy) {
console.warn('[KAFKA] Health degraded - high pending messages or disconnected');
}
}, 5000);
}
async sendLogBatch(logData, metadata = {}) {
if (!this.isConnected) {
throw new Error('Kafka producer not connected');
}
if (!this.healthy) {
throw new Error('Kafka producer unhealthy - not accepting new messages');
}
const messageId = ++this.messageId;
const message = {
id: messageId,
timestamp: Date.now(),
data: logData,
metadata: {
source: this.config.filename || 'unknown',
batchSize: logData.length,
...metadata
}
};
try {
const result = await this.producer.send({
topic: this.config.kafkaTopic,
messages: [{
key: `log-${messageId}`,
value: JSON.stringify(message),
headers: {
'content-type': 'application/json',
'source': this.config.filename || 'unknown',
'batch-id': messageId.toString()
}
}],
timeout: this.config.kafkaTimeout || 30000
});
logSuccess();
console.log(`[KAFKA] Successfully sent batch ${messageId} to partition ${result[0].partition}, offset ${result[0].baseOffset}`);
return {
success: true,
messageId,
partition: result[0].partition,
offset: result[0].baseOffset
};
} catch (error) {
logFailure();
logRetry();
const retries = (this.retryCounts.get(messageId) || 0) + 1;
this.retryCounts.set(messageId, retries);
console.error(`[KAFKA] Error sending batch ${messageId}:`, error.message);
if (retries >= this.config.kafkaMaxRetries || 5) {
console.error(`[KAFKA] Max retries reached for batch ${messageId}`);
this.retryCounts.delete(messageId);
return {
success: false,
messageId,
error: error.message,
retries
};
} else {
// Retry with exponential backoff
const retryDelay = Math.min(1000 * Math.pow(2, retries), 30000);
console.log(`[KAFKA] Retrying batch ${messageId} in ${retryDelay}ms (attempt ${retries})`);
setTimeout(async () => {
try {
await this.sendLogBatch(logData, metadata);
} catch (retryError) {
console.error(`[KAFKA] Retry failed for batch ${messageId}:`, retryError.message);
}
}, retryDelay);
return {
success: false,
messageId,
error: error.message,
retries,
retryScheduled: true
};
}
}
}
async sendLogEntry(logEntry, metadata = {}) {
return this.sendLogBatch([logEntry], metadata);
}
getStatus() {
return {
connected: this.isConnected,
healthy: this.healthy,
pendingMessages: this.pendingMessages.size,
retryCounts: this.retryCounts.size,
messageId: this.messageId
};
}
}
module.exports = KafkaProducer;