@itentialopensource/adapter-kafkav2 0.16.1 → 0.16.2

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/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
1
 
2
+ ## 0.16.2 [08-29-2023]
3
+
4
+ * Big fixes logLevel and adapter restart
5
+
6
+ See merge request itentialopensource/adapters/notification-messaging/adapter-kafkav2!15
7
+
8
+ ---
9
+
2
10
  ## 0.16.1 [08-24-2023]
3
11
 
4
12
  * Handle empty topics file
package/adapter.js CHANGED
@@ -22,6 +22,8 @@ const util = require('util');
22
22
  const AdapterBaseCl = require(path.join(__dirname, 'adapterBase.js'));
23
23
  const request = require('request');
24
24
 
25
+ let needRestart = false;
26
+
25
27
  function consoleLoggerProvider(name) {
26
28
  return {
27
29
  debug: log.debug,
@@ -33,6 +35,14 @@ function consoleLoggerProvider(name) {
33
35
 
34
36
  const { Kafka, logLevel } = require('kafkajs');
35
37
 
38
+ const LogLevelEnum = {
39
+ NOTHING: logLevel.NOTHING,
40
+ ERROR: logLevel.ERROR,
41
+ WARN: logLevel.WARN,
42
+ INFO: logLevel.INFO,
43
+ DEBUG: logLevel.DEBUG
44
+ };
45
+
36
46
  const pjson = require(path.resolve(__dirname, 'pronghorn.json'));
37
47
 
38
48
  let myid = null;
@@ -42,6 +52,14 @@ let firstDone = false;
42
52
  const mytopics = Object.keys(pjson.topics);
43
53
  const pronghornFile = path.join(__dirname, '/pronghorn.json');
44
54
 
55
+ // Function to convert a log level string to the corresponding enum value
56
+ function convertLogLevel(logLevelStr) {
57
+ // Remove 'logLevel.' prefix if it exists
58
+ const logLevelStrClean = logLevelStr.replace('logLevel.', '');
59
+ const logLevelEnum = LogLevelEnum[logLevelStrClean];
60
+ return logLevelEnum !== undefined ? logLevelEnum : logLevel.INFO; // Default to INFO if not found
61
+ }
62
+
45
63
  async function fetchSchema(registryUrl, topic, version) {
46
64
  return new Promise((resolve, reject) => {
47
65
  log.debug(`${registryUrl}/subjects/${topic}/versions/${version}`);
@@ -251,9 +269,7 @@ class Kafkav2 extends AdapterBaseCl {
251
269
  }
252
270
 
253
271
  // If we have a rabbit queue - need to add that to topics.json
254
- // if there are topics in the properties
255
272
  if (this.props && this.props.topics) {
256
- let needRestart = false;
257
273
  for (let t = 0; t < this.props.topics.length; t += 1) {
258
274
  if (typeof this.props.topics[t] === 'string') {
259
275
  const topName = this.props.topics[t];
@@ -485,229 +501,243 @@ class Kafkav2 extends AdapterBaseCl {
485
501
  return;
486
502
  }
487
503
 
488
- try {
489
- // Create a kafka client
490
- const combinedProps = this.props.client || {};
491
- if (this.props.client.ssl && this.props.client.ssl.ca) {
492
- combinedProps.ssl.ca = [fs.readFileSync(this.props.client.ssl.ca, 'utf-8')];
493
- }
494
- if (this.props.client.ssl && this.props.client.ssl.cert) {
495
- combinedProps.ssl.cert = fs.readFileSync(this.props.client.ssl.cert, 'utf-8');
496
- }
497
- if (this.props.client.ssl && this.props.client.ssl.key) {
498
- combinedProps.ssl.key = fs.readFileSync(this.props.client.ssl.key, 'utf-8');
499
- }
504
+ if (!needRestart) {
505
+ try {
506
+ if (this.props.client.logLevel) {
507
+ const logLevelEnum = convertLogLevel(this.props.client.logLevel);
508
+ this.props.client.logLevel = logLevelEnum;
509
+ }
510
+ // Create a kafka client
511
+ const combinedProps = this.props.client || {};
512
+ if (this.props.client.ssl && this.props.client.ssl.ca) {
513
+ combinedProps.ssl.ca = [fs.readFileSync(this.props.client.ssl.ca, 'utf-8')];
514
+ }
515
+ if (this.props.client.ssl && this.props.client.ssl.cert) {
516
+ combinedProps.ssl.cert = fs.readFileSync(this.props.client.ssl.cert, 'utf-8');
517
+ }
518
+ if (this.props.client.ssl && this.props.client.ssl.key) {
519
+ combinedProps.ssl.key = fs.readFileSync(this.props.client.ssl.key, 'utf-8');
520
+ }
500
521
 
501
- this.KafkaClient = new Kafka(combinedProps);
522
+ this.KafkaClient = new Kafka(combinedProps);
502
523
 
503
- if (this.props.client && this.props.client.producersasl) {
504
- combinedProps.sasl = this.props.client.producersasl;
505
- this.KafkaProducerClient = new Kafka(combinedProps);
506
- this.producer = this.KafkaProducerClient.producer(this.props.producer || {});
507
- } else {
508
- this.producer = this.KafkaClient.producer(this.props.producer || {});
509
- }
524
+ if (this.props.client && this.props.client.producersasl) {
525
+ combinedProps.sasl = this.props.client.producersasl;
526
+ this.KafkaProducerClient = new Kafka(combinedProps);
527
+ this.producer = this.KafkaProducerClient.producer(this.props.producer || {});
528
+ } else {
529
+ this.producer = this.KafkaClient.producer(this.props.producer || {});
530
+ }
510
531
 
511
- if (this.props.client && this.props.client.consumersasl) {
512
- combinedProps.sasl = this.props.client.consumersasl;
513
- this.KafkaConsumerClient = new Kafka(combinedProps);
514
- this.consumer = this.KafkaConsumerClient.consumer(this.props.consumer || {});
515
- } else {
516
- this.consumer = this.KafkaClient.consumer(this.props.consumer || {});
517
- }
532
+ if (this.props.client && this.props.client.consumersasl) {
533
+ combinedProps.sasl = this.props.client.consumersasl;
534
+ this.KafkaConsumerClient = new Kafka(combinedProps);
535
+ this.consumer = this.KafkaConsumerClient.consumer(this.props.consumer || {});
536
+ } else {
537
+ this.consumer = this.KafkaClient.consumer(this.props.consumer || {});
538
+ }
518
539
 
519
- const isPassingFiltering = (topic, message, filters) => {
520
- const topicConfig = this.topicsEvents.find((e) => e.topic === topic);
540
+ const isPassingFiltering = (topic, message, filters) => {
541
+ const topicConfig = this.topicsEvents.find((e) => e.topic === topic);
521
542
 
522
- if (!topicConfig) return true;
543
+ if (!topicConfig) return true;
523
544
 
524
- if (!topicConfig.subInfo || !Array.isArray(topicConfig.subInfo)) return true;
525
- if (filters.length === 0) return true;
526
- const matched = filters.find((filter) => {
527
- const re = new RegExp(filter);
528
- if (message.value.toString().match(re)) {
529
- log.debug('Matched filter: ', re);
530
- return true;
531
- }
532
- return false;
533
- });
534
- return matched;
535
- };
545
+ if (!topicConfig.subInfo || !Array.isArray(topicConfig.subInfo)) return true;
546
+ if (filters.length === 0) return true;
547
+ const matched = filters.find((filter) => {
548
+ const re = new RegExp(filter);
549
+ if (message.value.toString().match(re)) {
550
+ log.debug('Matched filter: ', re);
551
+ return true;
552
+ }
553
+ return false;
554
+ });
555
+ return matched;
556
+ };
536
557
 
537
- const run = async () => {
538
- this.emit('ONLINE', {
539
- id: this.id
540
- });
541
- log.info('EMITTED ONLINE');
542
- // Start consuming
543
- // Iterate through all topics in topicsEvents
544
- const topics = [];
545
- for (let t = 0; t < this.topicsEvents.length; t += 1) {
546
- topics.push(this.topicsEvents[t].topic);
547
- }
548
- await this.consumer.connect();
549
- await this.consumer.subscribe({ topics, fromBeginning: this.props.fromBeginning || false });
550
-
551
- let isAppActive = null;
552
- const topicPartitions = topics.map((topic) => ({ topic }));
553
-
554
- // if any IAP apps are down, pause consumer before it consumes any messages
555
- if (this.props && this.props.check_iap_apps) {
556
- isAppActive = await super.checkIapAppsStatus();
557
- if (!isAppActive) {
558
- log.debug('Pause consumer since IAP apps are down');
559
- // wait for the consumer to connect to Kafka before pausing it
560
- this.consumer.on(this.consumer.events.CONNECT, async () => {
561
- this.consumer.pause(topicPartitions);
562
- });
558
+ const run = async () => {
559
+ this.emit('ONLINE', {
560
+ id: this.id
561
+ });
562
+ log.info('EMITTED ONLINE');
563
+ // Start consuming
564
+ // Iterate through all topics in topicsEvents
565
+ const topics = [];
566
+ for (let t = 0; t < this.topicsEvents.length; t += 1) {
567
+ topics.push(this.topicsEvents[t].topic);
563
568
  }
564
- }
569
+ await this.consumer.connect();
570
+ await this.consumer.subscribe({ topics, fromBeginning: this.props.fromBeginning || false });
571
+
572
+ let isAppActive = null;
573
+ const topicPartitions = topics.map((topic) => ({ topic }));
565
574
 
566
- // keep checking IAP apps status
567
- if (this.props && this.props.check_iap_apps) {
568
- const intTime = this.props.iap_apps_check_interval || 30000;
569
- setInterval(async () => {
575
+ // if any IAP apps are down, pause consumer before it consumes any messages
576
+ if (this.props && this.props.check_iap_apps) {
570
577
  isAppActive = await super.checkIapAppsStatus();
571
- if (isAppActive) {
572
- log.debug('Resume consumer since IAP apps are active');
573
- this.consumer.resume(topicPartitions);
574
- } else {
575
- log.debug('Keep pausing consumer until IAP apps are active');
576
- this.consumer.pause(topicPartitions);
578
+ if (!isAppActive) {
579
+ log.debug('Pause consumer since IAP apps are down');
580
+ // wait for the consumer to connect to Kafka before pausing it
581
+ this.consumer.on(this.consumer.events.CONNECT, async () => {
582
+ this.consumer.pause(topicPartitions);
583
+ });
577
584
  }
578
- }, intTime);
579
- }
580
- await this.consumer.run({
581
- eachMessage: async ({ topic, partition, message }) => {
582
- log.info(`PROCESSING NEW MESSAGE ON TOPIC: ${topic} PARTITION: ${partition} WITH OFFSET: ${message.offset}`);
583
- const newMsg = message;
584
- let avroUsed = false;
585
- let desiredTopic = [];
586
- let allowedPartition = false;
587
-
588
- // if using avro
589
- if (this.registry && avroUsed) {
590
- // will give us an interval between 5001 and 60000 milliseconds (5 seconds to 1 minute)
591
- const fastInt = Math.floor(Math.random() * 55000) + 5001;
585
+ }
592
586
 
593
- // This interval is to help prevent the adapter from bombarding the registry by sending requests at random intervals
594
- const intervalObject = setInterval(async () => {
595
- // Prevents running the request while the first one is running
596
- if (!firstRun) {
597
- if (!firstDone) {
598
- firstRun = true;
599
- }
587
+ // keep checking IAP apps status
588
+ if (this.props && this.props.check_iap_apps) {
589
+ const intTime = this.props.iap_apps_check_interval || 30000;
590
+ setInterval(async () => {
591
+ isAppActive = await super.checkIapAppsStatus();
592
+ if (isAppActive) {
593
+ log.debug('Resume consumer since IAP apps are active');
594
+ // wait for the consumer to connect to Kafka before resuming it
595
+ this.consumer.on(this.consumer.events.CONNECT, async () => {
596
+ this.consumer.resume(topicPartitions);
597
+ });
598
+ } else {
599
+ log.debug('Keep pausing consumer until IAP apps are active');
600
+ // wait for the consumer to connect to Kafka before pausing it
601
+ this.consumer.on(this.consumer.events.CONNECT, async () => {
602
+ this.consumer.pause(topicPartitions);
603
+ });
604
+ }
605
+ }, intTime);
606
+ }
607
+ await this.consumer.run({
608
+ eachMessage: async ({ topic, partition, message }) => {
609
+ log.info(`PROCESSING NEW MESSAGE ON TOPIC: ${topic} PARTITION: ${partition} WITH OFFSET: ${message.offset}`);
610
+ const newMsg = message;
611
+ let avroUsed = false;
612
+ let desiredTopic = [];
613
+ let allowedPartition = false;
614
+
615
+ // if using avro
616
+ if (this.registry && avroUsed) {
617
+ // will give us an interval between 5001 and 60000 milliseconds (5 seconds to 1 minute)
618
+ const fastInt = Math.floor(Math.random() * 55000) + 5001;
619
+
620
+ // This interval is to help prevent the adapter from bombarding the registry by sending requests at random intervals
621
+ const intervalObject = setInterval(async () => {
622
+ // Prevents running the request while the first one is running
623
+ if (!firstRun) {
624
+ if (!firstDone) {
625
+ firstRun = true;
626
+ }
600
627
 
601
- try {
602
- log.debug('GET AVRO KEY');
603
- newMsg.key = await this.registry.decode(newMsg.key);
604
- log.debug(`AVRO KEY: ${newMsg.key}`);
605
- newMsg.value = await this.registry.decode(newMsg.value);
606
- log.debug(`AVRO MSG: ${newMsg.value}`);
607
- firstDone = true;
608
- firstRun = false;
609
- clearInterval(intervalObject);
610
- } catch (ex) {
611
- log.warn(`Had issue getting registry will try again in ${fastInt} milliseconds`);
612
- firstRun = false;
613
- }
614
- }
615
- }, fastInt);
616
- }
617
- // find the topic here to change the offset
618
- for (let t = 0; t < this.topicsEvents.length; t += 1) {
619
- if (this.topicsEvents[t].topic === topic && (this.topicsEvents[t].partition === partition || this.topicsEvents[t].partition.includes(partition))) {
620
- allowedPartition = true;
621
- if (this.topicsEvents[t].offset < message.offset) {
622
- this.topicsEvents[t].offset = message.offset;
623
- }
624
- // determine if we are using AVRO and set flag
625
- if (this.topicsEvents[t].avro && this.topicsEvents[t].avro.toUpperCase() === 'YES') {
626
- avroUsed = true;
627
- }
628
- // set desired topic for messages passing filtering
629
- if (this.topicsEvents[t].subInfo) {
630
- for (let k = 0; k < this.topicsEvents[t].subInfo.length; k += 1) {
631
- let passesFiltering = true;
632
- if (this.topicsEvents[t].subInfo[k].filters) {
633
- passesFiltering = isPassingFiltering(topic, newMsg, this.topicsEvents[t].subInfo[k].filters);
628
+ try {
629
+ log.debug('GET AVRO KEY');
630
+ newMsg.key = await this.registry.decode(newMsg.key);
631
+ log.debug(`AVRO KEY: ${newMsg.key}`);
632
+ newMsg.value = await this.registry.decode(newMsg.value);
633
+ log.debug(`AVRO MSG: ${newMsg.value}`);
634
+ firstDone = true;
635
+ firstRun = false;
636
+ clearInterval(intervalObject);
637
+ } catch (ex) {
638
+ log.warn(`Had issue getting registry will try again in ${fastInt} milliseconds`);
639
+ firstRun = false;
634
640
  }
635
- if (this.topicsEvents[t].subInfo[k].rabbit && passesFiltering) {
636
- desiredTopic.push(this.topicsEvents[t].subInfo[k].rabbit);
637
- } else if (passesFiltering) {
638
- desiredTopic.push(this.topicsEvents[t].topic);
641
+ }
642
+ }, fastInt);
643
+ }
644
+ // find the topic here to change the offset
645
+ for (let t = 0; t < this.topicsEvents.length; t += 1) {
646
+ if (this.topicsEvents[t].topic === topic && (this.topicsEvents[t].partition === partition || this.topicsEvents[t].partition.includes(partition))) {
647
+ allowedPartition = true;
648
+ if (this.topicsEvents[t].offset < message.offset) {
649
+ this.topicsEvents[t].offset = message.offset;
650
+ }
651
+ // determine if we are using AVRO and set flag
652
+ if (this.topicsEvents[t].avro && this.topicsEvents[t].avro.toUpperCase() === 'YES') {
653
+ avroUsed = true;
654
+ }
655
+ // set desired topic for messages passing filtering
656
+ if (this.topicsEvents[t].subInfo) {
657
+ for (let k = 0; k < this.topicsEvents[t].subInfo.length; k += 1) {
658
+ let passesFiltering = true;
659
+ if (this.topicsEvents[t].subInfo[k].filters) {
660
+ passesFiltering = isPassingFiltering(topic, newMsg, this.topicsEvents[t].subInfo[k].filters);
661
+ }
662
+ if (this.topicsEvents[t].subInfo[k].rabbit && passesFiltering) {
663
+ desiredTopic.push(this.topicsEvents[t].subInfo[k].rabbit);
664
+ } else if (passesFiltering) {
665
+ desiredTopic.push(this.topicsEvents[t].topic);
666
+ }
639
667
  }
640
668
  }
669
+ break;
641
670
  }
642
- break;
643
671
  }
644
- }
645
672
 
646
- if (desiredTopic.length !== 0 && allowedPartition) {
647
- log.info(`Message: '${message.value.toString()}' being CONSUMED as it does meet the filter condition`);
648
- if (desiredTopic.every((val) => mytopics.includes(val))) {
649
- log.info(`Emitting: ${message.value.toString()} TO ${desiredTopic}`);
650
- } else {
651
- log.info(`The desired topic ${desiredTopic} does not exist. If it should, make sure it is in pronghorn.json`);
652
- log.info(`Emitting: ${newMsg.value.toString()} TO kafka`);
653
- desiredTopic = 'kafka';
654
- }
655
- if (this.props.parseMessage || this.props.parseMessage === undefined) {
656
- let messageObj;
657
- const wrapper = this.props.wrapMessage || 'payload';
658
- try {
659
- messageObj = { [wrapper]: JSON.parse(newMsg.value) };
660
- } catch (ex) {
661
- messageObj = { [wrapper]: newMsg.value.toString() };
673
+ if (desiredTopic.length !== 0 && allowedPartition) {
674
+ log.info(`Message: '${message.value.toString()}' being CONSUMED as it does meet the filter condition`);
675
+ if (desiredTopic.every((val) => mytopics.includes(val))) {
676
+ log.info(`Emitting: ${message.value.toString()} TO ${desiredTopic}`);
677
+ } else {
678
+ log.info(`The desired topic ${desiredTopic} does not exist. If it should, make sure it is in pronghorn.json`);
679
+ log.info(`Emitting: ${newMsg.value.toString()} TO kafka`);
680
+ desiredTopic = 'kafka';
662
681
  }
663
- for (let j = 0; j < desiredTopic.length; j += 1) {
664
- messageObj.topic = topic;
665
- eventSystem.publish(desiredTopic[j], messageObj);
682
+ if (this.props.parseMessage || this.props.parseMessage === undefined) {
683
+ let messageObj;
684
+ const wrapper = this.props.wrapMessage || 'payload';
685
+ try {
686
+ messageObj = { [wrapper]: JSON.parse(newMsg.value) };
687
+ } catch (ex) {
688
+ messageObj = { [wrapper]: newMsg.value.toString() };
689
+ }
690
+ for (let j = 0; j < desiredTopic.length; j += 1) {
691
+ messageObj.topic = topic;
692
+ eventSystem.publish(desiredTopic[j], messageObj);
693
+ }
694
+ } else {
695
+ for (let j = 0; j < desiredTopic.length; j += 1) {
696
+ newMsg.topic = topic;
697
+ eventSystem.publish(desiredTopic[j], newMsg);
698
+ }
666
699
  }
667
700
  } else {
668
- for (let j = 0; j < desiredTopic.length; j += 1) {
669
- newMsg.topic = topic;
670
- eventSystem.publish(desiredTopic[j], newMsg);
671
- }
701
+ log.info(`Message: '${newMsg.value.toString()}' being DROPPED as it fails to meet any filter condition`);
672
702
  }
673
- } else {
674
- log.info(`Message: '${newMsg.value.toString()}' being DROPPED as it fails to meet any filter condition`);
675
703
  }
676
- }
677
- });
678
- };
679
- run().catch((err) => console.error(`[consumer] ${err.message}`, err));
704
+ });
705
+ };
706
+ run().catch((err) => console.error(`[consumer] ${err.message}`, err));
680
707
 
681
- const errorTypes = ['unhandledRejection', 'uncaughtException'];
682
- const signalTraps = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
708
+ const errorTypes = ['unhandledRejection', 'uncaughtException'];
709
+ const signalTraps = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
683
710
 
684
- errorTypes.forEach((type) => {
685
- process.on(type, async (e) => {
686
- try {
687
- console.log(`process.on ${type}`);
688
- console.error(e);
689
- await this.consumer.disconnect();
690
- process.exit(0);
691
- } catch (_) {
692
- process.exit(1);
693
- }
711
+ errorTypes.forEach((type) => {
712
+ process.on(type, async (e) => {
713
+ try {
714
+ console.log(`process.on ${type}`);
715
+ console.error(e);
716
+ await this.consumer.disconnect();
717
+ process.exit(0);
718
+ } catch (_) {
719
+ process.exit(1);
720
+ }
721
+ });
694
722
  });
695
- });
696
723
 
697
- signalTraps.forEach((type) => {
698
- process.once(type, async () => {
699
- try {
700
- await this.consumer.disconnect();
701
- } finally {
702
- process.kill(process.pid, type);
703
- }
724
+ signalTraps.forEach((type) => {
725
+ process.once(type, async () => {
726
+ try {
727
+ await this.consumer.disconnect();
728
+ } finally {
729
+ process.kill(process.pid, type);
730
+ }
731
+ });
704
732
  });
705
- });
706
- } catch (ex) {
707
- const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
708
- log.error(JSON.stringify(ex));
709
- log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
710
- return (null, errorObj);
733
+ } catch (ex) {
734
+ const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
735
+ log.error(JSON.stringify(ex));
736
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
737
+ return (null, errorObj);
738
+ }
739
+ } else {
740
+ log.debug('Waiting for adapter to come back up');
711
741
  }
712
742
  }
713
743
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itentialopensource/adapter-kafkav2",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "description": "Itential adapter to connect to kafka",
5
5
  "main": "adapter.js",
6
6
  "wizardVersion": "2.35.0",
Binary file