@jetit/publisher 5.6.1 → 5.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -10,7 +10,11 @@ class PrometheusAdapter {
|
|
|
10
10
|
constructor(streams, promClient) {
|
|
11
11
|
this.streams = streams;
|
|
12
12
|
this.promClient = promClient;
|
|
13
|
-
this.registry = new this.promClient.Registry(
|
|
13
|
+
this.registry = new this.promClient.Registry();
|
|
14
|
+
this.promClient.collectDefaultMetrics({
|
|
15
|
+
register: this.registry,
|
|
16
|
+
timeout: 60000,
|
|
17
|
+
});
|
|
14
18
|
this.initializeMetrics();
|
|
15
19
|
}
|
|
16
20
|
initializeMetrics() {
|
|
@@ -159,10 +163,10 @@ class PrometheusAdapter {
|
|
|
159
163
|
}
|
|
160
164
|
});
|
|
161
165
|
Object.entries(metrics.redisCommandLatencies).forEach(([command, latency]) => {
|
|
162
|
-
this.redisCommandLatency.set({ command }, latency);
|
|
166
|
+
this.redisCommandLatency.set({ command }, latency?.total / latency?.count || 0);
|
|
163
167
|
});
|
|
164
168
|
Object.entries(metrics.consumerLag).forEach(([consumerGroup, lag]) => {
|
|
165
|
-
this.consumerLag.set({ consumer_group: consumerGroup }, lag);
|
|
169
|
+
this.consumerLag.set({ consumer_group: consumerGroup }, lag?.total / lag?.count || 0);
|
|
166
170
|
});
|
|
167
171
|
}
|
|
168
172
|
/**
|
|
@@ -171,7 +175,7 @@ class PrometheusAdapter {
|
|
|
171
175
|
setupEndpoint(app, endPoint = '/metrics') {
|
|
172
176
|
app.get(endPoint, async (_, res) => {
|
|
173
177
|
await this.updateMetrics();
|
|
174
|
-
res.
|
|
178
|
+
res.header('Content-Type', this.registry.contentType);
|
|
175
179
|
res.send(await this.registry.metrics());
|
|
176
180
|
});
|
|
177
181
|
}
|
|
@@ -69,7 +69,7 @@ class MetricsTracker {
|
|
|
69
69
|
return i === 0 ? `0-${buckets[i]}` : `${buckets[i - 1] + 1}-${buckets[i]}`;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
return
|
|
72
|
+
return `10000-100000`;
|
|
73
73
|
}
|
|
74
74
|
addRedisCommandLatency(command, time) {
|
|
75
75
|
if (!this.metrics.redisCommandLatencies[command]) {
|
package/src/lib/redis/streams.js
CHANGED
|
@@ -62,13 +62,14 @@ class Streams {
|
|
|
62
62
|
this.instanceUniqueId = process.env['INSTANCE_ID'] ?? (0, id_1.generateID)('HEX', 'FE');
|
|
63
63
|
this.instanceId = `${serviceName}:${this.instanceUniqueId}`;
|
|
64
64
|
this.consumerGroupName = `cg-${serviceName}`;
|
|
65
|
-
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Instance ID: ${this.instanceId}`);
|
|
65
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Instance ID: ${this.instanceId} and with config: ${JSON.stringify(this.config)}`);
|
|
66
66
|
const cleanUpInterval = this.config.cleanUpInterval ?? parseInt(process.env['CLEANUP_INTERVAL'] ?? `${this.config.cleanUpInterval}`, 10);
|
|
67
67
|
this.cleanUpTimer = setInterval(() => {
|
|
68
68
|
this.runClear(cleanUpInterval).catch((error) => {
|
|
69
|
-
logger_1.PUBLISHER_LOGGER.error('Error during cleanup:', error);
|
|
69
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Error during cleanup:', error);
|
|
70
70
|
});
|
|
71
71
|
}, cleanUpInterval);
|
|
72
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Clean Up process setup for ${cleanUpInterval} ms`);
|
|
72
73
|
this.dlq = new dlq_1.DeadLetterQueue(this.redisPublisher, config.dlqEventThreshold);
|
|
73
74
|
this.metricsCollector = new collector_1.MetricsCollector({
|
|
74
75
|
redisClient: this.redisPublisher,
|
|
@@ -82,7 +83,7 @@ class Streams {
|
|
|
82
83
|
}
|
|
83
84
|
setupCircuitBreakerListeners() {
|
|
84
85
|
this.circuitBreaker.on('stateChange', async (newState) => {
|
|
85
|
-
logger_1.PUBLISHER_LOGGER.log(`Circuit breaker state changed to: ${circuit_breaker_1.CircuitState[newState]}`);
|
|
86
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Circuit breaker state changed to: ${circuit_breaker_1.CircuitState[newState]}`);
|
|
86
87
|
if (newState === circuit_breaker_1.CircuitState.CLOSED) {
|
|
87
88
|
await this.processStoredEvents();
|
|
88
89
|
}
|
|
@@ -92,10 +93,10 @@ class Streams {
|
|
|
92
93
|
logger_1.PUBLISHER_LOGGER.log('PUBLISHER: Running Clearance', this.eventsListened);
|
|
93
94
|
const cleanupPromises = this.eventsListened.map((eventName) => this.cleanupAcknowledgedMessages(eventName, cleanUpInterval)
|
|
94
95
|
.then(() => {
|
|
95
|
-
logger_1.PUBLISHER_LOGGER.log(`Cleanup process for Acknowledged messages completed for ${eventName}`);
|
|
96
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Cleanup process for Acknowledged messages completed for ${eventName}`);
|
|
96
97
|
})
|
|
97
98
|
.catch((error) => {
|
|
98
|
-
logger_1.PUBLISHER_LOGGER.error(`Error during cleanup for ${eventName}:`, error);
|
|
99
|
+
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER: Error during cleanup for ${eventName}:`, error);
|
|
99
100
|
}));
|
|
100
101
|
await Promise.all(cleanupPromises);
|
|
101
102
|
}
|
|
@@ -133,7 +134,7 @@ class Streams {
|
|
|
133
134
|
*/
|
|
134
135
|
if (this.config.circuitBreaker.enabled && !this.circuitBreaker.isAllowed()) {
|
|
135
136
|
await this.circuitBreaker.storeEvent(data);
|
|
136
|
-
logger_1.PUBLISHER_LOGGER.error('Circuit is open, event stored for later processing');
|
|
137
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Circuit is open, event stored for later processing');
|
|
137
138
|
return 'CIRCUIT_BREAKER_FLOW';
|
|
138
139
|
}
|
|
139
140
|
try {
|
|
@@ -264,14 +265,14 @@ class Streams {
|
|
|
264
265
|
// If both exist, this will be a no-op
|
|
265
266
|
try {
|
|
266
267
|
await this.redisGroups.xgroup('CREATE', streamName, this.consumerGroupName, '0', 'MKSTREAM');
|
|
267
|
-
logger_1.PUBLISHER_LOGGER.log(`Group created for ${JSON.stringify({ streamName, cgn: this.consumerGroupName })}`);
|
|
268
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Group created for ${JSON.stringify({ streamName, cgn: this.consumerGroupName })}`);
|
|
268
269
|
}
|
|
269
270
|
catch (e) {
|
|
270
271
|
// BUSYGROUP error means group already exists, which is fine
|
|
271
272
|
if (!e.message.includes('BUSYGROUP')) {
|
|
272
273
|
throw e;
|
|
273
274
|
}
|
|
274
|
-
logger_1.PUBLISHER_LOGGER.log(`Group already exists for ${JSON.stringify({ streamName, cgn: this.consumerGroupName })}`);
|
|
275
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Group already exists for ${JSON.stringify({ streamName, cgn: this.consumerGroupName })}`);
|
|
275
276
|
}
|
|
276
277
|
// Create consumer (idempotent operation)
|
|
277
278
|
const createConsumerStatus = await this.redisGroups.xgroup('CREATECONSUMER', streamName, this.consumerGroupName, this.instanceId);
|
|
@@ -320,11 +321,11 @@ class Streams {
|
|
|
320
321
|
await processMessage(this.redisGroups, '0', new tracker_1.MetricsTracker(), false);
|
|
321
322
|
}
|
|
322
323
|
catch (error) {
|
|
323
|
-
logger_1.PUBLISHER_LOGGER.error('Error in running recurring cleanup task:', error);
|
|
324
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Error in running recurring cleanup task:', error);
|
|
324
325
|
}
|
|
325
326
|
},
|
|
326
327
|
error: (error) => {
|
|
327
|
-
logger_1.PUBLISHER_LOGGER.error('Fatal error in cleanup timer:', error);
|
|
328
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Fatal error in cleanup timer:', error);
|
|
328
329
|
},
|
|
329
330
|
});
|
|
330
331
|
// Create observable with proper cleanup
|
|
@@ -374,7 +375,7 @@ class Streams {
|
|
|
374
375
|
* processed gets picked up by another instance, leading to multiple publications
|
|
375
376
|
*/
|
|
376
377
|
if (processPending) {
|
|
377
|
-
const claimed = await redisClient.xclaim(streamName, this.consumerGroupName, this.instanceId,
|
|
378
|
+
const claimed = await redisClient.xclaim(streamName, this.consumerGroupName, this.instanceId, 10000, messageId, 'JUSTID');
|
|
378
379
|
if (!claimed || claimed.length === 0) {
|
|
379
380
|
return; // Message already claimed or acknowledged by another consumer, so don't repush to the subscriber
|
|
380
381
|
}
|
|
@@ -393,22 +394,30 @@ class Streams {
|
|
|
393
394
|
else {
|
|
394
395
|
const messages = (await redisClient.xreadgroup('GROUP', this.consumerGroupName, this.instanceId, 'COUNT', 1, 'STREAMS', streamName, '>'));
|
|
395
396
|
if (messages?.length) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
const messageIdRead = messages[0][1][0][0];
|
|
398
|
+
if (messageIdRead !== messageId) {
|
|
399
|
+
if (messageId === '0') {
|
|
400
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Reprocessing unprocessed message with id: ${messageId}`);
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
const [timestamp] = messageIdRead?.split('-').map(Number);
|
|
404
|
+
const [publishedTimestamp] = messageId?.split('-').map(Number);
|
|
405
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Message queue processing is ${publishedTimestamp > timestamp ? 'ahead' : 'behind'}`);
|
|
406
|
+
}
|
|
399
407
|
}
|
|
408
|
+
messageId = messageIdRead;
|
|
400
409
|
try {
|
|
401
410
|
eventData = JSON.parse(messages[0][1][0][1][1]);
|
|
402
411
|
}
|
|
403
412
|
catch (error) {
|
|
404
|
-
logger_1.PUBLISHER_LOGGER.error(`JSON parsing failed for message: ${messages[0][1][0][1][1]}`);
|
|
413
|
+
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER: JSON parsing failed for message: ${messages[0][1][0][1][1]}`);
|
|
405
414
|
return;
|
|
406
415
|
}
|
|
407
416
|
}
|
|
408
417
|
}
|
|
409
418
|
}
|
|
410
419
|
catch (error) {
|
|
411
|
-
logger_1.PUBLISHER_LOGGER.error('Error retrieving or parsing event data:', error);
|
|
420
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Error retrieving or parsing event data:', error);
|
|
412
421
|
return;
|
|
413
422
|
}
|
|
414
423
|
finally {
|
|
@@ -444,7 +453,7 @@ class Streams {
|
|
|
444
453
|
}
|
|
445
454
|
catch (error) {
|
|
446
455
|
// Log error but don't fail entire processing
|
|
447
|
-
logger_1.PUBLISHER_LOGGER.error(`Error processing subscription ${subId}:`, error);
|
|
456
|
+
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER: Error processing subscription ${subId}:`, error);
|
|
448
457
|
}
|
|
449
458
|
}));
|
|
450
459
|
}
|
|
@@ -460,7 +469,7 @@ class Streams {
|
|
|
460
469
|
tracker.setConsumerLag(this.consumerGroupName, currentTime - eventData.createdAt);
|
|
461
470
|
}
|
|
462
471
|
catch (error) {
|
|
463
|
-
logger_1.PUBLISHER_LOGGER.error(`Processing error for message ${messageId}:`, error);
|
|
472
|
+
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER: Processing error for message ${messageId}:`, error);
|
|
464
473
|
const dlqEvent = {
|
|
465
474
|
...eventData,
|
|
466
475
|
failureReason: error.message,
|
|
@@ -602,7 +611,7 @@ class Streams {
|
|
|
602
611
|
}
|
|
603
612
|
}
|
|
604
613
|
catch (error) {
|
|
605
|
-
logger_1.PUBLISHER_LOGGER.error('Error during cleanup:', error);
|
|
614
|
+
logger_1.PUBLISHER_LOGGER.error('PUBLISHER: Error during cleanup:', error);
|
|
606
615
|
}
|
|
607
616
|
}
|
|
608
617
|
async cleanupAcknowledgedMessages(eventName, interval = this.config.acknowledgedMessageCleanupInterval) {
|
|
@@ -610,27 +619,34 @@ class Streams {
|
|
|
610
619
|
const lastAckKey = `last_ack:${streamName}`;
|
|
611
620
|
const oneHourAgo = Date.now() - interval;
|
|
612
621
|
try {
|
|
613
|
-
|
|
614
|
-
const groupInfo = (await this.redisGroups.xinfo('GROUPS', streamName));
|
|
615
|
-
// If no active consumers, leave stream as is
|
|
616
|
-
if (!groupInfo || !groupInfo.some((group) => group.consumers > 0)) {
|
|
617
|
-
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: No active consumers for ${streamName}, leaving stream as is`);
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
622
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Started [cleanupAcknowledgedMessages] for ${streamName} and with cleanUpInterval set as ${interval}`);
|
|
620
623
|
// Get last acknowledged message ID
|
|
621
624
|
const lastAckId = await this.redisGroups.get(lastAckKey);
|
|
625
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: [cleanupAcknowledgedMessages] {lastAckId: ${lastAckId}, lastAckKey: ${lastAckKey}} `);
|
|
622
626
|
if (!lastAckId) {
|
|
623
|
-
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: No acknowledged messages for ${streamName}`);
|
|
627
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: [cleanupAcknowledgedMessages]No acknowledged messages for ${streamName}`);
|
|
624
628
|
return;
|
|
625
629
|
}
|
|
626
630
|
// Extract timestamp from message ID
|
|
627
631
|
const [timestamp] = lastAckId.split('-').map(Number);
|
|
628
632
|
const cleanupThreshold = Math.min(timestamp, oneHourAgo);
|
|
633
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: [cleanupAcknowledgedMessages] {cleanUpThreshold: ${cleanupThreshold}} `);
|
|
634
|
+
// Get consumer group info to check if consumers are active
|
|
635
|
+
const groupInfo = (await this.redisGroups.xinfo('GROUPS', streamName))?.map(([, name, , consumers]) => ({
|
|
636
|
+
name: name,
|
|
637
|
+
consumers: consumers,
|
|
638
|
+
}));
|
|
639
|
+
// If no active consumers, leave stream as is
|
|
640
|
+
if (groupInfo?.length === 0 || !groupInfo?.some((group) => group.consumers > 0)) {
|
|
641
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: No active consumers for ${streamName}, leaving stream as is`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: [cleanupAcknowledgedMessages] XTRIM to be called`);
|
|
629
645
|
await this.redisGroups.xtrim(streamName, 'MINID', cleanupThreshold);
|
|
630
646
|
logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Cleaned up messages before last acknowledged message ${timestamp} from ${streamName}`);
|
|
631
647
|
}
|
|
632
648
|
catch (error) {
|
|
633
|
-
logger_1.PUBLISHER_LOGGER.error(`Error during cleanup for ${streamName}:`, error);
|
|
649
|
+
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER: Error during cleanup for ${streamName}:`, error);
|
|
634
650
|
}
|
|
635
651
|
}
|
|
636
652
|
async getDiagnosticData(events) {
|
|
@@ -759,7 +775,7 @@ class Streams {
|
|
|
759
775
|
]);
|
|
760
776
|
}
|
|
761
777
|
catch (error) {
|
|
762
|
-
logger_1.PUBLISHER_LOGGER.error(`Error acknowledging message ${messageId} for ${streamName}:`, error);
|
|
778
|
+
logger_1.PUBLISHER_LOGGER.error(`PUBLISHER: Error acknowledging message ${messageId} for ${streamName}:`, error);
|
|
763
779
|
throw error;
|
|
764
780
|
}
|
|
765
781
|
}
|