@omen.foundation/node-microservice-runtime 0.1.48 → 0.1.50

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.
@@ -82,6 +82,7 @@ let globalCollectorProcess = null;
82
82
  let globalCollectorStartError = null;
83
83
  let globalCollectorExitCode = null;
84
84
  let globalCollectorStderr = [];
85
+ let globalCollectorStartupPromise = null;
85
86
  let globalCollectorInitError = null;
86
87
  function calculateSignature(pid, secret, uriPathAndQuery, body = null, version = '1') {
87
88
  let dataToSign = `${secret}${pid}${version}${uriPathAndQuery}`;
@@ -640,56 +641,102 @@ async function discoverOrStartCollector(logger, standardOtelEnabled, env) {
640
641
  if (!standardOtelEnabled) {
641
642
  return null;
642
643
  }
643
- const status = await isCollectorRunning();
644
- if (status.isRunning && status.isReady && status.otlpEndpoint) {
645
- logger.info(`[Collector] Found running collector at ${status.otlpEndpoint}`);
646
- return `http://${status.otlpEndpoint}`;
644
+ if (globalCollectorStartupPromise) {
645
+ logger.info('[Collector] Collector startup already in progress, waiting for existing startup to complete...');
646
+ try {
647
+ const result = await globalCollectorStartupPromise;
648
+ globalCollectorStartupPromise = null;
649
+ return result;
650
+ }
651
+ catch (error) {
652
+ logger.error(`[Collector] Existing startup promise failed: ${error instanceof Error ? error.message : String(error)}`);
653
+ globalCollectorStartupPromise = null;
654
+ }
647
655
  }
648
- try {
649
- globalCollectorInitError = null;
650
- logger.info('[Collector] Starting OpenTelemetry collector...');
651
- const { endpoint } = await startCollector(logger, undefined, env);
652
- await new Promise(resolve => setTimeout(resolve, 200));
653
- if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
654
- const errorMsg = `Collector process exited immediately with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
655
- globalCollectorInitError = errorMsg;
656
- logger.error(`[Collector] ${errorMsg}`);
657
- return null;
656
+ let existingEndpoint;
657
+ if (globalCollectorProcess) {
658
+ const processAlive = globalCollectorProcess.exitCode === null &&
659
+ globalCollectorProcess.killed === false;
660
+ if (processAlive) {
661
+ logger.info(`[Collector] Collector process already exists (PID ${globalCollectorProcess.pid}), waiting for it to be ready...`);
662
+ existingEndpoint = process.env.BEAM_OTLP_HTTP_ENDPOINT ?
663
+ `http://${process.env.BEAM_OTLP_HTTP_ENDPOINT}` :
664
+ 'http://0.0.0.0:4318';
658
665
  }
659
- const maxWaitTime = 60000;
660
- const checkInterval = 500;
661
- const maxChecks = Math.floor(maxWaitTime / checkInterval);
662
- logger.info('[Collector] Waiting for collector to become ready...');
663
- for (let i = 0; i < maxChecks; i++) {
664
- await new Promise(resolve => setTimeout(resolve, checkInterval));
666
+ else {
667
+ logger.warn(`[Collector] Previous collector process (PID ${globalCollectorProcess.pid}) is dead, starting new one...`);
668
+ globalCollectorProcess = null;
669
+ globalCollectorExitCode = null;
670
+ globalCollectorStderr = [];
671
+ }
672
+ }
673
+ if (!globalCollectorProcess) {
674
+ const status = await isCollectorRunning();
675
+ if (status.isRunning && status.isReady && status.otlpEndpoint) {
676
+ logger.info(`[Collector] Found running collector at ${status.otlpEndpoint}`);
677
+ return `http://${status.otlpEndpoint}`;
678
+ }
679
+ }
680
+ const startupPromise = (async () => {
681
+ try {
682
+ globalCollectorInitError = null;
683
+ let endpoint;
684
+ if (existingEndpoint) {
685
+ endpoint = existingEndpoint;
686
+ logger.info(`[Collector] Waiting for existing collector to become ready at ${endpoint}...`);
687
+ }
688
+ else {
689
+ logger.info('[Collector] Starting OpenTelemetry collector...');
690
+ const startResult = await startCollector(logger, undefined, env);
691
+ endpoint = startResult.endpoint;
692
+ await new Promise(resolve => setTimeout(resolve, 200));
693
+ if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
694
+ const errorMsg = `Collector process exited immediately with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
695
+ globalCollectorInitError = errorMsg;
696
+ logger.error(`[Collector] ${errorMsg}`);
697
+ return null;
698
+ }
699
+ }
700
+ const maxWaitTime = 60000;
701
+ const checkInterval = 500;
702
+ const maxChecks = Math.floor(maxWaitTime / checkInterval);
703
+ logger.info('[Collector] Waiting for collector to become ready...');
704
+ for (let i = 0; i < maxChecks; i++) {
705
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
706
+ if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
707
+ const errorMsg = `Collector process exited during startup with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
708
+ globalCollectorInitError = errorMsg;
709
+ logger.error(`[Collector] ${errorMsg}`);
710
+ return null;
711
+ }
712
+ const newStatus = await isCollectorRunning();
713
+ if (newStatus.isRunning && newStatus.isReady) {
714
+ logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint}`);
715
+ return newStatus.otlpEndpoint ? `http://${newStatus.otlpEndpoint}` : endpoint;
716
+ }
717
+ if (i > 0 && i % 4 === 0) {
718
+ logger.info(`[Collector] Still waiting for collector to become ready... (${(i * checkInterval) / 1000}s elapsed)`);
719
+ }
720
+ }
665
721
  if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
666
- const errorMsg = `Collector process exited during startup with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
722
+ const errorMsg = `Collector process exited with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
667
723
  globalCollectorInitError = errorMsg;
668
724
  logger.error(`[Collector] ${errorMsg}`);
669
725
  return null;
670
726
  }
671
- const newStatus = await isCollectorRunning();
672
- if (newStatus.isRunning && newStatus.isReady) {
673
- logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint}`);
674
- return newStatus.otlpEndpoint ? `http://${newStatus.otlpEndpoint}` : endpoint;
675
- }
676
- if (i > 0 && i % 4 === 0) {
677
- logger.info(`[Collector] Still waiting for collector to become ready... (${(i * checkInterval) / 1000}s elapsed)`);
678
- }
727
+ logger.error(`[Collector] Collector did not become ready within ${maxWaitTime / 1000} seconds`);
728
+ return null;
679
729
  }
680
- if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
681
- const errorMsg = `Collector process exited with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
730
+ catch (err) {
731
+ const errorMsg = err instanceof Error ? err.message : String(err);
682
732
  globalCollectorInitError = errorMsg;
683
- logger.error(`[Collector] ${errorMsg}`);
733
+ logger.error(`[Collector] Failed to start collector: ${errorMsg}`);
684
734
  return null;
685
735
  }
686
- logger.error(`[Collector] Collector did not become ready within ${maxWaitTime / 1000} seconds`);
687
- return null;
688
- }
689
- catch (err) {
690
- const errorMsg = err instanceof Error ? err.message : String(err);
691
- globalCollectorInitError = errorMsg;
692
- logger.error(`[Collector] Failed to start collector: ${errorMsg}`);
693
- return null;
694
- }
736
+ })();
737
+ globalCollectorStartupPromise = startupPromise;
738
+ startupPromise.finally(() => {
739
+ globalCollectorStartupPromise = null;
740
+ });
741
+ return await startupPromise;
695
742
  }
@@ -1 +1 @@
1
- {"version":3,"file":"collector-manager.d.ts","sourceRoot":"","sources":["../src/collector-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,YAAY,EAAE,MAAM,eAAe,CAAC;AAOpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAYpD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqCD;;GAEG;AACH,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAqCD;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,qBAAqB,CAAC,CAwChC;AA0OD;;GAEG;AACH,wBAAgB,8BAA8B,IAAI;IAChD,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,aAAa,GAAG,KAAK,GAAG,SAAS,CAAC;CAC3C,CAoBA;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC,CAyEnE;AA0BD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAYjG;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,iBAAiB,EACtB,SAAS,GAAE,MAAc,GACxB,MAAM,GAAG,IAAI,CA8Ef;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,GAAG,CAAC,EAAE,iBAAiB,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgNtD;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI;IAC3C,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CASA;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,mBAAmB,EAAE,OAAO,EAC5B,GAAG,CAAC,EAAE,iBAAiB,GACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgFxB"}
1
+ {"version":3,"file":"collector-manager.d.ts","sourceRoot":"","sources":["../src/collector-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,YAAY,EAAE,MAAM,eAAe,CAAC;AAOpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAYpD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuCD;;GAEG;AACH,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAqCD;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,qBAAqB,CAAC,CAwChC;AA0OD;;GAEG;AACH,wBAAgB,8BAA8B,IAAI;IAChD,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,aAAa,GAAG,KAAK,GAAG,SAAS,CAAC;CAC3C,CAoBA;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC,CAyEnE;AA0BD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAYjG;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,iBAAiB,EACtB,SAAS,GAAE,MAAc,GACxB,MAAM,GAAG,IAAI,CA8Ef;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,GAAG,CAAC,EAAE,iBAAiB,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgNtD;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI;IAC3C,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CASA;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,mBAAmB,EAAE,OAAO,EAC5B,GAAG,CAAC,EAAE,iBAAiB,GACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmJxB"}
@@ -41,6 +41,8 @@ let globalCollectorProcess = null;
41
41
  let globalCollectorStartError = null;
42
42
  let globalCollectorExitCode = null;
43
43
  let globalCollectorStderr = [];
44
+ // Track if collector startup is in progress to prevent duplicate starts
45
+ let globalCollectorStartupPromise = null;
44
46
  let globalCollectorInitError = null; // Tracks errors from discoverOrStartCollector
45
47
  /**
46
48
  * Calculates Beamable signature for signed requests
@@ -744,69 +746,133 @@ export async function discoverOrStartCollector(logger, standardOtelEnabled, env)
744
746
  if (!standardOtelEnabled) {
745
747
  return null;
746
748
  }
747
- // First, check if collector is already running
748
- const status = await isCollectorRunning();
749
- if (status.isRunning && status.isReady && status.otlpEndpoint) {
750
- logger.info(`[Collector] Found running collector at ${status.otlpEndpoint}`);
751
- return `http://${status.otlpEndpoint}`;
749
+ // CRITICAL: Check if collector startup is already in progress
750
+ // This prevents duplicate collector starts if this function is called multiple times
751
+ // (e.g., if setupCollectorBeforeLogging times out but the promise is still running)
752
+ if (globalCollectorStartupPromise) {
753
+ logger.info('[Collector] Collector startup already in progress, waiting for existing startup to complete...');
754
+ try {
755
+ const result = await globalCollectorStartupPromise;
756
+ // Clear the promise after it completes (success or failure)
757
+ globalCollectorStartupPromise = null;
758
+ return result;
759
+ }
760
+ catch (error) {
761
+ logger.error(`[Collector] Existing startup promise failed: ${error instanceof Error ? error.message : String(error)}`);
762
+ // Clear the promise so we can retry
763
+ globalCollectorStartupPromise = null;
764
+ // Fall through to start a new one
765
+ }
752
766
  }
753
- // Collector not running - start it
754
- try {
755
- // Clear any previous init error
756
- globalCollectorInitError = null;
757
- logger.info('[Collector] Starting OpenTelemetry collector...');
758
- const { endpoint } = await startCollector(logger, undefined, env);
759
- // Check if collector process exited immediately (crashed)
760
- // Wait a bit longer to see if it crashes right after starting
761
- await new Promise(resolve => setTimeout(resolve, 200));
762
- if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
763
- const errorMsg = `Collector process exited immediately with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
764
- globalCollectorInitError = errorMsg;
765
- logger.error(`[Collector] ${errorMsg}`);
766
- return null;
767
+ // CRITICAL: Check if we already have a collector process starting/running
768
+ // This prevents duplicate collector starts if this function is called multiple times
769
+ let existingEndpoint;
770
+ if (globalCollectorProcess) {
771
+ // Check if process is still alive
772
+ const processAlive = globalCollectorProcess.exitCode === null &&
773
+ globalCollectorProcess.killed === false;
774
+ if (processAlive) {
775
+ logger.info(`[Collector] Collector process already exists (PID ${globalCollectorProcess.pid}), waiting for it to be ready...`);
776
+ // Use the configured endpoint from environment (we started it earlier)
777
+ existingEndpoint = process.env.BEAM_OTLP_HTTP_ENDPOINT ?
778
+ `http://${process.env.BEAM_OTLP_HTTP_ENDPOINT}` :
779
+ 'http://0.0.0.0:4318';
780
+ // Fall through to the wait logic below (don't start a new collector)
781
+ }
782
+ else {
783
+ // Process is dead, clear it and start fresh
784
+ logger.warn(`[Collector] Previous collector process (PID ${globalCollectorProcess.pid}) is dead, starting new one...`);
785
+ globalCollectorProcess = null;
786
+ globalCollectorExitCode = null;
787
+ globalCollectorStderr = [];
767
788
  }
768
- // CRITICAL: Wait for collector to be fully ready before returning
769
- // We'll wait up to 60 seconds, checking every 500ms
770
- // This ensures the collector is actually ready to receive logs before we continue
771
- const maxWaitTime = 60000; // 60 seconds
772
- const checkInterval = 500; // Check every 500ms
773
- const maxChecks = Math.floor(maxWaitTime / checkInterval);
774
- logger.info('[Collector] Waiting for collector to become ready...');
775
- for (let i = 0; i < maxChecks; i++) {
776
- await new Promise(resolve => setTimeout(resolve, checkInterval));
777
- // Check if process exited during wait
789
+ }
790
+ // First, check if collector is already running (via UDP discovery)
791
+ if (!globalCollectorProcess) {
792
+ const status = await isCollectorRunning();
793
+ if (status.isRunning && status.isReady && status.otlpEndpoint) {
794
+ logger.info(`[Collector] Found running collector at ${status.otlpEndpoint}`);
795
+ return `http://${status.otlpEndpoint}`;
796
+ }
797
+ }
798
+ // Collector not running - start it (or wait for existing one to be ready)
799
+ // Wrap the entire startup logic in a promise that we track globally
800
+ // This prevents duplicate starts if this function is called multiple times
801
+ const startupPromise = (async () => {
802
+ try {
803
+ // Clear any previous init error
804
+ globalCollectorInitError = null;
805
+ let endpoint;
806
+ if (existingEndpoint) {
807
+ // Collector already starting, just wait for it to be ready
808
+ endpoint = existingEndpoint;
809
+ logger.info(`[Collector] Waiting for existing collector to become ready at ${endpoint}...`);
810
+ }
811
+ else {
812
+ // Start a new collector
813
+ logger.info('[Collector] Starting OpenTelemetry collector...');
814
+ const startResult = await startCollector(logger, undefined, env);
815
+ endpoint = startResult.endpoint;
816
+ // Check if collector process exited immediately (crashed)
817
+ // Wait a bit longer to see if it crashes right after starting
818
+ await new Promise(resolve => setTimeout(resolve, 200));
819
+ if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
820
+ const errorMsg = `Collector process exited immediately with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
821
+ globalCollectorInitError = errorMsg;
822
+ logger.error(`[Collector] ${errorMsg}`);
823
+ return null;
824
+ }
825
+ }
826
+ // CRITICAL: Wait for collector to be fully ready before returning
827
+ // We'll wait up to 60 seconds, checking every 500ms
828
+ // This ensures the collector is actually ready to receive logs before we continue
829
+ const maxWaitTime = 60000; // 60 seconds
830
+ const checkInterval = 500; // Check every 500ms
831
+ const maxChecks = Math.floor(maxWaitTime / checkInterval);
832
+ logger.info('[Collector] Waiting for collector to become ready...');
833
+ for (let i = 0; i < maxChecks; i++) {
834
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
835
+ // Check if process exited during wait
836
+ if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
837
+ const errorMsg = `Collector process exited during startup with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
838
+ globalCollectorInitError = errorMsg;
839
+ logger.error(`[Collector] ${errorMsg}`);
840
+ return null;
841
+ }
842
+ const newStatus = await isCollectorRunning();
843
+ if (newStatus.isRunning && newStatus.isReady) {
844
+ logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint}`);
845
+ return newStatus.otlpEndpoint ? `http://${newStatus.otlpEndpoint}` : endpoint;
846
+ }
847
+ // Log progress every 2 seconds
848
+ if (i > 0 && i % 4 === 0) {
849
+ logger.info(`[Collector] Still waiting for collector to become ready... (${(i * checkInterval) / 1000}s elapsed)`);
850
+ }
851
+ }
852
+ // Check one more time if process exited
778
853
  if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
779
- const errorMsg = `Collector process exited during startup with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
854
+ const errorMsg = `Collector process exited with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
780
855
  globalCollectorInitError = errorMsg;
781
856
  logger.error(`[Collector] ${errorMsg}`);
782
857
  return null;
783
858
  }
784
- const newStatus = await isCollectorRunning();
785
- if (newStatus.isRunning && newStatus.isReady) {
786
- logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint}`);
787
- return newStatus.otlpEndpoint ? `http://${newStatus.otlpEndpoint}` : endpoint;
788
- }
789
- // Log progress every 2 seconds
790
- if (i > 0 && i % 4 === 0) {
791
- logger.info(`[Collector] Still waiting for collector to become ready... (${(i * checkInterval) / 1000}s elapsed)`);
792
- }
859
+ // Collector did not become ready within timeout
860
+ logger.error(`[Collector] Collector did not become ready within ${maxWaitTime / 1000} seconds`);
861
+ return null;
793
862
  }
794
- // Check one more time if process exited
795
- if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
796
- const errorMsg = `Collector process exited with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
863
+ catch (err) {
864
+ const errorMsg = err instanceof Error ? err.message : String(err);
797
865
  globalCollectorInitError = errorMsg;
798
- logger.error(`[Collector] ${errorMsg}`);
866
+ logger.error(`[Collector] Failed to start collector: ${errorMsg}`);
799
867
  return null;
800
868
  }
801
- // Collector did not become ready within timeout
802
- logger.error(`[Collector] Collector did not become ready within ${maxWaitTime / 1000} seconds`);
803
- return null;
804
- }
805
- catch (err) {
806
- const errorMsg = err instanceof Error ? err.message : String(err);
807
- globalCollectorInitError = errorMsg;
808
- logger.error(`[Collector] Failed to start collector: ${errorMsg}`);
809
- return null;
810
- }
869
+ })();
870
+ // Store the promise globally so other calls to this function can wait for it
871
+ globalCollectorStartupPromise = startupPromise;
872
+ // Clear the promise when it completes (so we don't keep waiting on old promises)
873
+ startupPromise.finally(() => {
874
+ globalCollectorStartupPromise = null;
875
+ });
876
+ return await startupPromise;
811
877
  }
812
878
  //# sourceMappingURL=collector-manager.js.map