@omen.foundation/node-microservice-runtime 0.1.55 → 0.1.57

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.
@@ -107,30 +107,42 @@ async function fetchClickHouseCredentials(env) {
107
107
  const apiUrl = (0, urls_js_1.hostToHttpUrl)(env.host);
108
108
  const uriPath = '/api/beamo/otel/auth/writer/config';
109
109
  const configUrl = new URL(uriPath, apiUrl).toString();
110
+ const verboseLogger = (0, pino_1.default)({ name: 'fetch-credentials', level: 'info' }, process.stdout);
111
+ verboseLogger.info(`[Credentials] Starting credential fetch from ${configUrl}...`);
110
112
  const secret = process.env.SECRET;
111
113
  if (!secret) {
114
+ verboseLogger.error('[Credentials] SECRET environment variable is missing');
112
115
  throw new Error('SECRET environment variable is required to fetch ClickHouse credentials');
113
116
  }
117
+ verboseLogger.info('[Credentials] SECRET found, calculating signature...');
114
118
  const pathAndQuery = getPathAndQuery(uriPath);
115
119
  const signature = calculateSignature(env.pid, secret, pathAndQuery, null, '1');
120
+ verboseLogger.info('[Credentials] Signature calculated, making API request...');
116
121
  const headers = {
117
122
  'Content-Type': 'application/json',
118
123
  Accept: 'application/json',
119
124
  'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,
120
125
  'X-BEAM-SIGNATURE': signature,
121
126
  };
127
+ const fetchStartTime = Date.now();
122
128
  const response = await fetch(configUrl, {
123
129
  method: 'GET',
124
130
  headers,
125
131
  });
132
+ const fetchElapsed = Date.now() - fetchStartTime;
133
+ verboseLogger.info(`[Credentials] API request completed in ${fetchElapsed}ms, status: ${response.status}`);
126
134
  if (!response.ok) {
127
135
  const errorText = await response.text().catch(() => 'Unknown error');
136
+ verboseLogger.error(`[Credentials] API request failed: ${response.status} ${response.statusText}`);
128
137
  throw new Error(`Failed to fetch ClickHouse credentials from ${configUrl}: ${response.status} ${response.statusText} - ${errorText.substring(0, 200)}`);
129
138
  }
139
+ verboseLogger.info('[Credentials] Parsing response JSON...');
130
140
  const credentials = await response.json();
131
141
  if (!credentials.endpoint || !credentials.username || !credentials.password) {
142
+ verboseLogger.error('[Credentials] Invalid response: missing required fields');
132
143
  throw new Error('Invalid ClickHouse credentials response: missing required fields');
133
144
  }
145
+ verboseLogger.info(`[Credentials] Successfully fetched credentials (endpoint: ${credentials.endpoint}, username: ${credentials.username})`);
134
146
  return credentials;
135
147
  }
136
148
  function getCollectorStoragePath() {
@@ -429,7 +441,7 @@ function addAuthEnvironmentVars(endpoint, username, password) {
429
441
  throw new Error(`Failed to set ClickHouse credentials in process.env - this should never happen in Node.js`);
430
442
  }
431
443
  }
432
- async function startCollectorAndWaitForReady(env, timeoutMs = 180000) {
444
+ async function startCollectorAndWaitForReady(env, _timeoutMs) {
433
445
  const isInDocker = process.env.IS_LOCAL !== '1' && process.env.IS_LOCAL !== 'true';
434
446
  const useLocalOtel = !!process.env.BEAM_LOCAL_OTEL;
435
447
  const standardOtelEnabled = (isInDocker || useLocalOtel) && !process.env.BEAM_DISABLE_STANDARD_OTEL;
@@ -442,49 +454,24 @@ async function startCollectorAndWaitForReady(env, timeoutMs = 180000) {
442
454
  level: 'info',
443
455
  }, process.stdout);
444
456
  initLogger.info('[OTLP] Setting up collector (waiting for readiness before enabling Portal logs)...');
445
- const startTime = Date.now();
446
- let endpoint = null;
447
- let completed = false;
448
- const setupPromise = discoverOrStartCollector(initLogger, standardOtelEnabled, env)
449
- .then((result) => {
450
- if (result) {
451
- return result;
452
- }
453
- return null;
454
- })
455
- .catch((error) => {
456
- initLogger.error(`[OTLP] Collector setup error: ${error instanceof Error ? error.message : String(error)}`);
457
- return null;
458
- });
459
- const timeoutPromise = new Promise((resolve) => {
460
- setTimeout(() => {
461
- if (!completed) {
462
- initLogger.warn(`[OTLP] Collector setup timeout after ${timeoutMs}ms, continuing without Portal logs`);
463
- completed = true;
464
- resolve(null);
465
- }
466
- }, timeoutMs);
467
- });
457
+ const setupStartTime = Date.now();
468
458
  try {
469
- const result = await Promise.race([setupPromise, timeoutPromise]);
470
- if (result && typeof result === 'string' && result.length > 0 && !completed) {
471
- endpoint = result;
472
- completed = true;
473
- const elapsed = Date.now() - startTime;
474
- initLogger.info(`[OTLP] Collector ready at ${endpoint} (took ${elapsed}ms). Portal logs now enabled.`);
459
+ initLogger.info('[OTLP] Step 1: Discovering or starting collector...');
460
+ const endpoint = await discoverOrStartCollector(initLogger, standardOtelEnabled, env);
461
+ const elapsed = Date.now() - setupStartTime;
462
+ if (endpoint) {
463
+ initLogger.info(`[OTLP] Collector ready at ${endpoint}. Portal logs now enabled. (took ${elapsed}ms)`);
475
464
  return endpoint;
476
465
  }
477
- if (!completed) {
478
- completed = true;
466
+ else {
467
+ initLogger.warn(`[OTLP] Collector setup failed, continuing without Portal logs. (took ${elapsed}ms)`);
468
+ return null;
479
469
  }
480
- return null;
481
470
  }
482
471
  catch (error) {
472
+ const elapsed = Date.now() - setupStartTime;
483
473
  const errorMsg = error instanceof Error ? error.message : String(error);
484
- initLogger.error(`[OTLP] Collector setup failed: ${errorMsg}`);
485
- if (!completed) {
486
- completed = true;
487
- }
474
+ initLogger.error(`[OTLP] Collector setup failed after ${elapsed}ms: ${errorMsg}`);
488
475
  return null;
489
476
  }
490
477
  }
@@ -592,7 +579,10 @@ async function startCollector(logger, otlpEndpoint, env) {
592
579
  if ((!clickhouseEndpoint || !clickhouseUsername || !clickhousePassword) && env) {
593
580
  try {
594
581
  logger.info('[Collector] Fetching ClickHouse credentials from Beamable API...');
582
+ const credStartTime = Date.now();
595
583
  const credentials = await fetchClickHouseCredentials(env);
584
+ const credElapsed = Date.now() - credStartTime;
585
+ logger.info(`[Collector] ClickHouse credentials fetch completed in ${credElapsed}ms`);
596
586
  clickhouseEndpoint = credentials.endpoint;
597
587
  clickhouseUsername = credentials.username;
598
588
  clickhousePassword = credentials.password;
@@ -617,8 +607,11 @@ async function startCollector(logger, otlpEndpoint, env) {
617
607
  logger.error(errorMsg);
618
608
  throw new Error(errorMsg);
619
609
  }
620
- logger.info('[Collector] Resolving collector binary and config...');
610
+ logger.info('[Collector] Step 2: Resolving collector binary and config...');
611
+ const resolveStartTime = Date.now();
621
612
  const collectorInfo = await resolveCollector(true, logger);
613
+ const resolveElapsed = Date.now() - resolveStartTime;
614
+ logger.info(`[Collector] Collector binary/config resolution completed in ${resolveElapsed}ms`);
622
615
  if (!collectorInfo.binaryPath) {
623
616
  logger.error('[Collector] Binary not found and download failed');
624
617
  throw new Error('Collector binary not found and download failed');
@@ -763,11 +756,14 @@ async function discoverOrStartCollector(logger, standardOtelEnabled, env) {
763
756
  }
764
757
  }
765
758
  if (!globalCollectorProcess) {
759
+ logger.info('[Collector] Checking for existing collector via UDP discovery...');
766
760
  const status = await isCollectorRunning();
761
+ logger.info(`[Collector] UDP discovery result: isRunning=${status.isRunning}, isReady=${status.isReady}, endpoint=${status.otlpEndpoint || 'none'}`);
767
762
  if (status.isRunning && status.isReady && status.otlpEndpoint) {
768
763
  logger.info(`[Collector] Found running collector at ${status.otlpEndpoint}`);
769
764
  return `http://${status.otlpEndpoint}`;
770
765
  }
766
+ logger.info('[Collector] No existing collector found, will start new one...');
771
767
  }
772
768
  const startupPromise = (async () => {
773
769
  try {
@@ -778,9 +774,13 @@ async function discoverOrStartCollector(logger, standardOtelEnabled, env) {
778
774
  logger.info(`[Collector] Waiting for existing collector to become ready at ${endpoint}...`);
779
775
  }
780
776
  else {
781
- logger.info('[Collector] Starting OpenTelemetry collector...');
777
+ logger.info('[Collector] Starting new OpenTelemetry collector...');
778
+ const startCollectorTime = Date.now();
782
779
  const startResult = await startCollector(logger, undefined, env);
780
+ const startCollectorElapsed = Date.now() - startCollectorTime;
781
+ logger.info(`[Collector] Collector process started in ${startCollectorElapsed}ms, endpoint: ${startResult.endpoint}`);
783
782
  endpoint = startResult.endpoint;
783
+ logger.info('[Collector] Checking if collector process is still running...');
784
784
  await new Promise(resolve => setTimeout(resolve, 200));
785
785
  if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
786
786
  const errorMsg = `Collector process exited immediately with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
@@ -788,26 +788,31 @@ async function discoverOrStartCollector(logger, standardOtelEnabled, env) {
788
788
  logger.error(`[Collector] ${errorMsg}`);
789
789
  return null;
790
790
  }
791
+ logger.info('[Collector] Collector process is still running, proceeding to readiness check...');
791
792
  }
792
793
  const maxWaitTime = 60000;
793
794
  const checkInterval = 500;
794
795
  const maxChecks = Math.floor(maxWaitTime / checkInterval);
795
- logger.info('[Collector] Waiting for collector to become ready...');
796
+ logger.info(`[Collector] Waiting for collector to become ready (checking every ${checkInterval}ms, max ${maxWaitTime / 1000}s)...`);
797
+ const readinessStartTime = Date.now();
796
798
  for (let i = 0; i < maxChecks; i++) {
797
799
  await new Promise(resolve => setTimeout(resolve, checkInterval));
800
+ const elapsed = Date.now() - readinessStartTime;
798
801
  if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
799
802
  const errorMsg = `Collector process exited during startup with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
800
803
  globalCollectorInitError = errorMsg;
801
- logger.error(`[Collector] ${errorMsg}`);
804
+ logger.error(`[Collector] ${errorMsg} (after ${elapsed}ms)`);
802
805
  return null;
803
806
  }
807
+ logger.info(`[Collector] Checking collector readiness... (${(elapsed / 1000).toFixed(1)}s elapsed, attempt ${i + 1}/${maxChecks})`);
804
808
  const newStatus = await isCollectorRunning();
809
+ logger.info(`[Collector] Collector status: isRunning=${newStatus.isRunning}, isReady=${newStatus.isReady}, pid=${newStatus.pid}, endpoint=${newStatus.otlpEndpoint || 'none'}`);
805
810
  if (newStatus.isRunning && newStatus.isReady) {
806
- logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint}`);
811
+ logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint} (ready after ${elapsed}ms)`);
807
812
  return newStatus.otlpEndpoint ? `http://${newStatus.otlpEndpoint}` : endpoint;
808
813
  }
809
- if (i > 0 && i % 4 === 0) {
810
- logger.info(`[Collector] Still waiting for collector to become ready... (${(i * checkInterval) / 1000}s elapsed)`);
814
+ if (i > 0 && i % 2 === 0) {
815
+ logger.info(`[Collector] Still waiting for collector to become ready... (${(elapsed / 1000).toFixed(1)}s elapsed)`);
811
816
  }
812
817
  }
813
818
  if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
@@ -45,7 +45,7 @@ export declare function addAuthEnvironmentVars(endpoint: string, username: strin
45
45
  * This ensures Portal logs (structured logs via OTLP) only start appearing AFTER collector is ready.
46
46
  * Returns the OTLP endpoint when ready, or null if it times out or fails.
47
47
  */
48
- export declare function startCollectorAndWaitForReady(env: EnvironmentConfig, timeoutMs?: number): Promise<string | null>;
48
+ export declare function startCollectorAndWaitForReady(env: EnvironmentConfig, _timeoutMs?: number): Promise<string | null>;
49
49
  /**
50
50
  * Starts the collector asynchronously in the background (non-blocking).
51
51
  * This allows the service to start immediately while collector downloads/starts.
@@ -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;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;AAiPD;;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;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,iBAAiB,EACtB,SAAS,GAAE,MAAe,GACzB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgFxB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAsChE;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"}
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,CAsDhC;AAiPD;;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;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,iBAAiB,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwCxB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAsChE;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,CAsNtD;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,CA+JxB"}
@@ -80,14 +80,20 @@ export async function fetchClickHouseCredentials(env) {
80
80
  const apiUrl = hostToHttpUrl(env.host);
81
81
  const uriPath = '/api/beamo/otel/auth/writer/config';
82
82
  const configUrl = new URL(uriPath, apiUrl).toString();
83
+ // Create a minimal logger for verbose output
84
+ const verboseLogger = pino({ name: 'fetch-credentials', level: 'info' }, process.stdout);
85
+ verboseLogger.info(`[Credentials] Starting credential fetch from ${configUrl}...`);
83
86
  // Get secret from environment (required for signed requests)
84
87
  const secret = process.env.SECRET;
85
88
  if (!secret) {
89
+ verboseLogger.error('[Credentials] SECRET environment variable is missing');
86
90
  throw new Error('SECRET environment variable is required to fetch ClickHouse credentials');
87
91
  }
92
+ verboseLogger.info('[Credentials] SECRET found, calculating signature...');
88
93
  // Calculate signature for signed request (matching C# HttpSignedRequester)
89
94
  const pathAndQuery = getPathAndQuery(uriPath);
90
95
  const signature = calculateSignature(env.pid, secret, pathAndQuery, null, '1');
96
+ verboseLogger.info('[Credentials] Signature calculated, making API request...');
91
97
  // Build headers with signed authentication (matching C# HttpSignedRequester)
92
98
  const headers = {
93
99
  'Content-Type': 'application/json',
@@ -95,18 +101,25 @@ export async function fetchClickHouseCredentials(env) {
95
101
  'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,
96
102
  'X-BEAM-SIGNATURE': signature,
97
103
  };
104
+ const fetchStartTime = Date.now();
98
105
  const response = await fetch(configUrl, {
99
106
  method: 'GET',
100
107
  headers,
101
108
  });
109
+ const fetchElapsed = Date.now() - fetchStartTime;
110
+ verboseLogger.info(`[Credentials] API request completed in ${fetchElapsed}ms, status: ${response.status}`);
102
111
  if (!response.ok) {
103
112
  const errorText = await response.text().catch(() => 'Unknown error');
113
+ verboseLogger.error(`[Credentials] API request failed: ${response.status} ${response.statusText}`);
104
114
  throw new Error(`Failed to fetch ClickHouse credentials from ${configUrl}: ${response.status} ${response.statusText} - ${errorText.substring(0, 200)}`);
105
115
  }
116
+ verboseLogger.info('[Credentials] Parsing response JSON...');
106
117
  const credentials = await response.json();
107
118
  if (!credentials.endpoint || !credentials.username || !credentials.password) {
119
+ verboseLogger.error('[Credentials] Invalid response: missing required fields');
108
120
  throw new Error('Invalid ClickHouse credentials response: missing required fields');
109
121
  }
122
+ verboseLogger.info(`[Credentials] Successfully fetched credentials (endpoint: ${credentials.endpoint}, username: ${credentials.username})`);
110
123
  return credentials;
111
124
  }
112
125
  /**
@@ -472,7 +485,7 @@ export function addAuthEnvironmentVars(endpoint, username, password) {
472
485
  * This ensures Portal logs (structured logs via OTLP) only start appearing AFTER collector is ready.
473
486
  * Returns the OTLP endpoint when ready, or null if it times out or fails.
474
487
  */
475
- export async function startCollectorAndWaitForReady(env, timeoutMs = 180000 // 3 minutes to allow for download + startup
488
+ export async function startCollectorAndWaitForReady(env, _timeoutMs // Not used - kept for API compatibility
476
489
  ) {
477
490
  // Match C# logic: (this.InDocker() || UseLocalOtel) && !BEAM_DISABLE_STANDARD_OTEL
478
491
  const isInDocker = process.env.IS_LOCAL !== '1' && process.env.IS_LOCAL !== 'true';
@@ -489,60 +502,25 @@ export async function startCollectorAndWaitForReady(env, timeoutMs = 180000 // 3
489
502
  level: 'info',
490
503
  }, process.stdout);
491
504
  initLogger.info('[OTLP] Setting up collector (waiting for readiness before enabling Portal logs)...');
492
- // Wait for collector to be ready with timeout
493
- const startTime = Date.now();
494
- let endpoint = null;
495
- let completed = false;
496
- // Start collector setup - wrap in a promise that handles errors
497
- const setupPromise = discoverOrStartCollector(initLogger, standardOtelEnabled, env)
498
- .then((result) => {
499
- if (result) {
500
- return result;
501
- }
502
- return null;
503
- })
504
- .catch((error) => {
505
- initLogger.error(`[OTLP] Collector setup error: ${error instanceof Error ? error.message : String(error)}`);
506
- return null;
507
- });
508
- // Set timeout - only resolve if setup hasn't completed
509
- const timeoutPromise = new Promise((resolve) => {
510
- setTimeout(() => {
511
- if (!completed) {
512
- initLogger.warn(`[OTLP] Collector setup timeout after ${timeoutMs}ms, continuing without Portal logs`);
513
- completed = true;
514
- resolve(null);
515
- }
516
- }, timeoutMs);
517
- });
518
- // Race between setup and timeout - wait for whichever completes first
519
- // But we need to check if setup actually succeeded (got a valid endpoint)
505
+ const setupStartTime = Date.now();
506
+ // Simple linear async/await - no timeouts, no Promise.race complexity
520
507
  try {
521
- const result = await Promise.race([setupPromise, timeoutPromise]);
522
- // Check if we got a valid endpoint (setup completed successfully)
523
- if (result && typeof result === 'string' && result.length > 0 && !completed) {
524
- endpoint = result;
525
- completed = true;
526
- const elapsed = Date.now() - startTime;
527
- initLogger.info(`[OTLP] Collector ready at ${endpoint} (took ${elapsed}ms). Portal logs now enabled.`);
508
+ initLogger.info('[OTLP] Step 1: Discovering or starting collector...');
509
+ const endpoint = await discoverOrStartCollector(initLogger, standardOtelEnabled, env);
510
+ const elapsed = Date.now() - setupStartTime;
511
+ if (endpoint) {
512
+ initLogger.info(`[OTLP] Collector ready at ${endpoint}. Portal logs now enabled. (took ${elapsed}ms)`);
528
513
  return endpoint;
529
514
  }
530
- // If we got here, either:
531
- // 1. Setup returned null (failed or timed out internally)
532
- // 2. Timeout promise resolved
533
- // 3. Result is not a valid endpoint string
534
- if (!completed) {
535
- completed = true;
515
+ else {
516
+ initLogger.warn(`[OTLP] Collector setup failed, continuing without Portal logs. (took ${elapsed}ms)`);
517
+ return null;
536
518
  }
537
- return null;
538
519
  }
539
520
  catch (error) {
540
- // Setup promise rejected - collector startup failed
521
+ const elapsed = Date.now() - setupStartTime;
541
522
  const errorMsg = error instanceof Error ? error.message : String(error);
542
- initLogger.error(`[OTLP] Collector setup failed: ${errorMsg}`);
543
- if (!completed) {
544
- completed = true;
545
- }
523
+ initLogger.error(`[OTLP] Collector setup failed after ${elapsed}ms: ${errorMsg}`);
546
524
  return null;
547
525
  }
548
526
  }
@@ -684,7 +662,10 @@ export async function startCollector(logger, otlpEndpoint, env) {
684
662
  if ((!clickhouseEndpoint || !clickhouseUsername || !clickhousePassword) && env) {
685
663
  try {
686
664
  logger.info('[Collector] Fetching ClickHouse credentials from Beamable API...');
665
+ const credStartTime = Date.now();
687
666
  const credentials = await fetchClickHouseCredentials(env);
667
+ const credElapsed = Date.now() - credStartTime;
668
+ logger.info(`[Collector] ClickHouse credentials fetch completed in ${credElapsed}ms`);
688
669
  clickhouseEndpoint = credentials.endpoint;
689
670
  clickhouseUsername = credentials.username;
690
671
  clickhousePassword = credentials.password;
@@ -716,8 +697,11 @@ export async function startCollector(logger, otlpEndpoint, env) {
716
697
  throw new Error(errorMsg);
717
698
  }
718
699
  // Now resolve collector binary and config (after credentials are fetched and set)
719
- logger.info('[Collector] Resolving collector binary and config...');
700
+ logger.info('[Collector] Step 2: Resolving collector binary and config...');
701
+ const resolveStartTime = Date.now();
720
702
  const collectorInfo = await resolveCollector(true, logger);
703
+ const resolveElapsed = Date.now() - resolveStartTime;
704
+ logger.info(`[Collector] Collector binary/config resolution completed in ${resolveElapsed}ms`);
721
705
  if (!collectorInfo.binaryPath) {
722
706
  logger.error('[Collector] Binary not found and download failed');
723
707
  throw new Error('Collector binary not found and download failed');
@@ -915,11 +899,14 @@ export async function discoverOrStartCollector(logger, standardOtelEnabled, env)
915
899
  }
916
900
  // First, check if collector is already running (via UDP discovery)
917
901
  if (!globalCollectorProcess) {
902
+ logger.info('[Collector] Checking for existing collector via UDP discovery...');
918
903
  const status = await isCollectorRunning();
904
+ logger.info(`[Collector] UDP discovery result: isRunning=${status.isRunning}, isReady=${status.isReady}, endpoint=${status.otlpEndpoint || 'none'}`);
919
905
  if (status.isRunning && status.isReady && status.otlpEndpoint) {
920
906
  logger.info(`[Collector] Found running collector at ${status.otlpEndpoint}`);
921
907
  return `http://${status.otlpEndpoint}`;
922
908
  }
909
+ logger.info('[Collector] No existing collector found, will start new one...');
923
910
  }
924
911
  // Collector not running - start it (or wait for existing one to be ready)
925
912
  // Wrap the entire startup logic in a promise that we track globally
@@ -936,11 +923,14 @@ export async function discoverOrStartCollector(logger, standardOtelEnabled, env)
936
923
  }
937
924
  else {
938
925
  // Start a new collector
939
- logger.info('[Collector] Starting OpenTelemetry collector...');
926
+ logger.info('[Collector] Starting new OpenTelemetry collector...');
927
+ const startCollectorTime = Date.now();
940
928
  const startResult = await startCollector(logger, undefined, env);
929
+ const startCollectorElapsed = Date.now() - startCollectorTime;
930
+ logger.info(`[Collector] Collector process started in ${startCollectorElapsed}ms, endpoint: ${startResult.endpoint}`);
941
931
  endpoint = startResult.endpoint;
942
932
  // Check if collector process exited immediately (crashed)
943
- // Wait a bit longer to see if it crashes right after starting
933
+ logger.info('[Collector] Checking if collector process is still running...');
944
934
  await new Promise(resolve => setTimeout(resolve, 200));
945
935
  if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
946
936
  const errorMsg = `Collector process exited immediately with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
@@ -948,6 +938,7 @@ export async function discoverOrStartCollector(logger, standardOtelEnabled, env)
948
938
  logger.error(`[Collector] ${errorMsg}`);
949
939
  return null;
950
940
  }
941
+ logger.info('[Collector] Collector process is still running, proceeding to readiness check...');
951
942
  }
952
943
  // CRITICAL: Wait for collector to be fully ready before returning
953
944
  // We'll wait up to 60 seconds, checking every 500ms
@@ -955,24 +946,28 @@ export async function discoverOrStartCollector(logger, standardOtelEnabled, env)
955
946
  const maxWaitTime = 60000; // 60 seconds
956
947
  const checkInterval = 500; // Check every 500ms
957
948
  const maxChecks = Math.floor(maxWaitTime / checkInterval);
958
- logger.info('[Collector] Waiting for collector to become ready...');
949
+ logger.info(`[Collector] Waiting for collector to become ready (checking every ${checkInterval}ms, max ${maxWaitTime / 1000}s)...`);
950
+ const readinessStartTime = Date.now();
959
951
  for (let i = 0; i < maxChecks; i++) {
960
952
  await new Promise(resolve => setTimeout(resolve, checkInterval));
953
+ const elapsed = Date.now() - readinessStartTime;
961
954
  // Check if process exited during wait
962
955
  if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
963
956
  const errorMsg = `Collector process exited during startup with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
964
957
  globalCollectorInitError = errorMsg;
965
- logger.error(`[Collector] ${errorMsg}`);
958
+ logger.error(`[Collector] ${errorMsg} (after ${elapsed}ms)`);
966
959
  return null;
967
960
  }
961
+ logger.info(`[Collector] Checking collector readiness... (${(elapsed / 1000).toFixed(1)}s elapsed, attempt ${i + 1}/${maxChecks})`);
968
962
  const newStatus = await isCollectorRunning();
963
+ logger.info(`[Collector] Collector status: isRunning=${newStatus.isRunning}, isReady=${newStatus.isReady}, pid=${newStatus.pid}, endpoint=${newStatus.otlpEndpoint || 'none'}`);
969
964
  if (newStatus.isRunning && newStatus.isReady) {
970
- logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint}`);
965
+ logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint} (ready after ${elapsed}ms)`);
971
966
  return newStatus.otlpEndpoint ? `http://${newStatus.otlpEndpoint}` : endpoint;
972
967
  }
973
- // Log progress every 2 seconds
974
- if (i > 0 && i % 4 === 0) {
975
- logger.info(`[Collector] Still waiting for collector to become ready... (${(i * checkInterval) / 1000}s elapsed)`);
968
+ // Log progress every second (every 2 checks)
969
+ if (i > 0 && i % 2 === 0) {
970
+ logger.info(`[Collector] Still waiting for collector to become ready... (${(elapsed / 1000).toFixed(1)}s elapsed)`);
976
971
  }
977
972
  }
978
973
  // Check one more time if process exited