@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.
- package/dist/collector-manager.cjs +49 -44
- package/dist/collector-manager.d.ts +1 -1
- package/dist/collector-manager.d.ts.map +1 -1
- package/dist/collector-manager.js +52 -57
- package/dist/collector-manager.js.map +1 -1
- package/dist/runtime.cjs +5 -18
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +8 -21
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/collector-manager.ts +54 -62
- package/src/runtime.ts +8 -20
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
478
|
-
|
|
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(
|
|
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 %
|
|
810
|
-
logger.info(`[Collector] Still waiting for collector to become ready... (${(
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
493
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
974
|
-
if (i > 0 && i %
|
|
975
|
-
logger.info(`[Collector] Still waiting for collector to become ready... (${(
|
|
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
|