@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetit/publisher",
3
- "version": "5.6.1",
3
+ "version": "5.6.3",
4
4
  "type": "commonjs",
5
5
  "dependencies": {
6
6
  "@jetit/id": "^0.0.13",
@@ -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({ collectDefaultMetrics: { timeout: 60000 } });
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.set('Content-Type', this.registry.contentType);
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 `>${buckets[buckets.length - 1]}`;
72
+ return `10000-100000`;
73
73
  }
74
74
  addRedisCommandLatency(command, time) {
75
75
  if (!this.metrics.redisCommandLatencies[command]) {
@@ -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, 20000, messageId, 'JUSTID');
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
- if (messageId === '0') {
397
- messageId = messages[0][1][0][0];
398
- logger_1.PUBLISHER_LOGGER.log(`PUBLISHER: Reprocessing unprocessed message with id: ${messageId}`);
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
- // Get consumer group info to check if consumers are active
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
  }