@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
package/src/collector-manager.ts
CHANGED
|
@@ -123,15 +123,22 @@ export async function fetchClickHouseCredentials(
|
|
|
123
123
|
const uriPath = '/api/beamo/otel/auth/writer/config';
|
|
124
124
|
const configUrl = new URL(uriPath, apiUrl).toString();
|
|
125
125
|
|
|
126
|
+
// Create a minimal logger for verbose output
|
|
127
|
+
const verboseLogger = pino({ name: 'fetch-credentials', level: 'info' }, process.stdout);
|
|
128
|
+
verboseLogger.info(`[Credentials] Starting credential fetch from ${configUrl}...`);
|
|
129
|
+
|
|
126
130
|
// Get secret from environment (required for signed requests)
|
|
127
131
|
const secret = process.env.SECRET;
|
|
128
132
|
if (!secret) {
|
|
133
|
+
verboseLogger.error('[Credentials] SECRET environment variable is missing');
|
|
129
134
|
throw new Error('SECRET environment variable is required to fetch ClickHouse credentials');
|
|
130
135
|
}
|
|
136
|
+
verboseLogger.info('[Credentials] SECRET found, calculating signature...');
|
|
131
137
|
|
|
132
138
|
// Calculate signature for signed request (matching C# HttpSignedRequester)
|
|
133
139
|
const pathAndQuery = getPathAndQuery(uriPath);
|
|
134
140
|
const signature = calculateSignature(env.pid, secret, pathAndQuery, null, '1');
|
|
141
|
+
verboseLogger.info('[Credentials] Signature calculated, making API request...');
|
|
135
142
|
|
|
136
143
|
// Build headers with signed authentication (matching C# HttpSignedRequester)
|
|
137
144
|
const headers: Record<string, string> = {
|
|
@@ -141,22 +148,29 @@ export async function fetchClickHouseCredentials(
|
|
|
141
148
|
'X-BEAM-SIGNATURE': signature,
|
|
142
149
|
};
|
|
143
150
|
|
|
151
|
+
const fetchStartTime = Date.now();
|
|
144
152
|
const response = await fetch(configUrl, {
|
|
145
153
|
method: 'GET',
|
|
146
154
|
headers,
|
|
147
155
|
});
|
|
156
|
+
const fetchElapsed = Date.now() - fetchStartTime;
|
|
157
|
+
verboseLogger.info(`[Credentials] API request completed in ${fetchElapsed}ms, status: ${response.status}`);
|
|
148
158
|
|
|
149
159
|
if (!response.ok) {
|
|
150
160
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
161
|
+
verboseLogger.error(`[Credentials] API request failed: ${response.status} ${response.statusText}`);
|
|
151
162
|
throw new Error(`Failed to fetch ClickHouse credentials from ${configUrl}: ${response.status} ${response.statusText} - ${errorText.substring(0, 200)}`);
|
|
152
163
|
}
|
|
153
164
|
|
|
165
|
+
verboseLogger.info('[Credentials] Parsing response JSON...');
|
|
154
166
|
const credentials = await response.json() as ClickHouseCredentials;
|
|
155
167
|
|
|
156
168
|
if (!credentials.endpoint || !credentials.username || !credentials.password) {
|
|
169
|
+
verboseLogger.error('[Credentials] Invalid response: missing required fields');
|
|
157
170
|
throw new Error('Invalid ClickHouse credentials response: missing required fields');
|
|
158
171
|
}
|
|
159
172
|
|
|
173
|
+
verboseLogger.info(`[Credentials] Successfully fetched credentials (endpoint: ${credentials.endpoint}, username: ${credentials.username})`);
|
|
160
174
|
return credentials;
|
|
161
175
|
}
|
|
162
176
|
|
|
@@ -555,7 +569,7 @@ export function addAuthEnvironmentVars(endpoint: string, username: string, passw
|
|
|
555
569
|
*/
|
|
556
570
|
export async function startCollectorAndWaitForReady(
|
|
557
571
|
env: EnvironmentConfig,
|
|
558
|
-
|
|
572
|
+
_timeoutMs?: number // Not used - kept for API compatibility
|
|
559
573
|
): Promise<string | null> {
|
|
560
574
|
// Match C# logic: (this.InDocker() || UseLocalOtel) && !BEAM_DISABLE_STANDARD_OTEL
|
|
561
575
|
const isInDocker = process.env.IS_LOCAL !== '1' && process.env.IS_LOCAL !== 'true';
|
|
@@ -575,65 +589,25 @@ export async function startCollectorAndWaitForReady(
|
|
|
575
589
|
}, process.stdout);
|
|
576
590
|
|
|
577
591
|
initLogger.info('[OTLP] Setting up collector (waiting for readiness before enabling Portal logs)...');
|
|
592
|
+
const setupStartTime = Date.now();
|
|
578
593
|
|
|
579
|
-
//
|
|
580
|
-
const startTime = Date.now();
|
|
581
|
-
let endpoint: string | null = null;
|
|
582
|
-
let completed = false;
|
|
583
|
-
|
|
584
|
-
// Start collector setup - wrap in a promise that handles errors
|
|
585
|
-
const setupPromise = discoverOrStartCollector(initLogger, standardOtelEnabled, env)
|
|
586
|
-
.then((result) => {
|
|
587
|
-
if (result) {
|
|
588
|
-
return result;
|
|
589
|
-
}
|
|
590
|
-
return null;
|
|
591
|
-
})
|
|
592
|
-
.catch((error) => {
|
|
593
|
-
initLogger.error(`[OTLP] Collector setup error: ${error instanceof Error ? error.message : String(error)}`);
|
|
594
|
-
return null;
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
// Set timeout - only resolve if setup hasn't completed
|
|
598
|
-
const timeoutPromise = new Promise<string | null>((resolve) => {
|
|
599
|
-
setTimeout(() => {
|
|
600
|
-
if (!completed) {
|
|
601
|
-
initLogger.warn(`[OTLP] Collector setup timeout after ${timeoutMs}ms, continuing without Portal logs`);
|
|
602
|
-
completed = true;
|
|
603
|
-
resolve(null);
|
|
604
|
-
}
|
|
605
|
-
}, timeoutMs);
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// Race between setup and timeout - wait for whichever completes first
|
|
609
|
-
// But we need to check if setup actually succeeded (got a valid endpoint)
|
|
594
|
+
// Simple linear async/await - no timeouts, no Promise.race complexity
|
|
610
595
|
try {
|
|
611
|
-
|
|
596
|
+
initLogger.info('[OTLP] Step 1: Discovering or starting collector...');
|
|
597
|
+
const endpoint = await discoverOrStartCollector(initLogger, standardOtelEnabled, env);
|
|
598
|
+
const elapsed = Date.now() - setupStartTime;
|
|
612
599
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
endpoint = result;
|
|
616
|
-
completed = true;
|
|
617
|
-
const elapsed = Date.now() - startTime;
|
|
618
|
-
initLogger.info(`[OTLP] Collector ready at ${endpoint} (took ${elapsed}ms). Portal logs now enabled.`);
|
|
600
|
+
if (endpoint) {
|
|
601
|
+
initLogger.info(`[OTLP] Collector ready at ${endpoint}. Portal logs now enabled. (took ${elapsed}ms)`);
|
|
619
602
|
return endpoint;
|
|
603
|
+
} else {
|
|
604
|
+
initLogger.warn(`[OTLP] Collector setup failed, continuing without Portal logs. (took ${elapsed}ms)`);
|
|
605
|
+
return null;
|
|
620
606
|
}
|
|
621
|
-
|
|
622
|
-
// If we got here, either:
|
|
623
|
-
// 1. Setup returned null (failed or timed out internally)
|
|
624
|
-
// 2. Timeout promise resolved
|
|
625
|
-
// 3. Result is not a valid endpoint string
|
|
626
|
-
if (!completed) {
|
|
627
|
-
completed = true;
|
|
628
|
-
}
|
|
629
|
-
return null;
|
|
630
607
|
} catch (error) {
|
|
631
|
-
|
|
608
|
+
const elapsed = Date.now() - setupStartTime;
|
|
632
609
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
633
|
-
initLogger.error(`[OTLP] Collector setup failed: ${errorMsg}`);
|
|
634
|
-
if (!completed) {
|
|
635
|
-
completed = true;
|
|
636
|
-
}
|
|
610
|
+
initLogger.error(`[OTLP] Collector setup failed after ${elapsed}ms: ${errorMsg}`);
|
|
637
611
|
return null;
|
|
638
612
|
}
|
|
639
613
|
}
|
|
@@ -796,7 +770,10 @@ export async function startCollector(
|
|
|
796
770
|
if ((!clickhouseEndpoint || !clickhouseUsername || !clickhousePassword) && env) {
|
|
797
771
|
try {
|
|
798
772
|
logger.info('[Collector] Fetching ClickHouse credentials from Beamable API...');
|
|
773
|
+
const credStartTime = Date.now();
|
|
799
774
|
const credentials = await fetchClickHouseCredentials(env);
|
|
775
|
+
const credElapsed = Date.now() - credStartTime;
|
|
776
|
+
logger.info(`[Collector] ClickHouse credentials fetch completed in ${credElapsed}ms`);
|
|
800
777
|
clickhouseEndpoint = credentials.endpoint;
|
|
801
778
|
clickhouseUsername = credentials.username;
|
|
802
779
|
clickhousePassword = credentials.password;
|
|
@@ -833,8 +810,11 @@ export async function startCollector(
|
|
|
833
810
|
}
|
|
834
811
|
|
|
835
812
|
// Now resolve collector binary and config (after credentials are fetched and set)
|
|
836
|
-
logger.info('[Collector] Resolving collector binary and config...');
|
|
813
|
+
logger.info('[Collector] Step 2: Resolving collector binary and config...');
|
|
814
|
+
const resolveStartTime = Date.now();
|
|
837
815
|
const collectorInfo = await resolveCollector(true, logger);
|
|
816
|
+
const resolveElapsed = Date.now() - resolveStartTime;
|
|
817
|
+
logger.info(`[Collector] Collector binary/config resolution completed in ${resolveElapsed}ms`);
|
|
838
818
|
|
|
839
819
|
if (!collectorInfo.binaryPath) {
|
|
840
820
|
logger.error('[Collector] Binary not found and download failed');
|
|
@@ -1067,11 +1047,14 @@ export async function discoverOrStartCollector(
|
|
|
1067
1047
|
|
|
1068
1048
|
// First, check if collector is already running (via UDP discovery)
|
|
1069
1049
|
if (!globalCollectorProcess) {
|
|
1050
|
+
logger.info('[Collector] Checking for existing collector via UDP discovery...');
|
|
1070
1051
|
const status = await isCollectorRunning();
|
|
1052
|
+
logger.info(`[Collector] UDP discovery result: isRunning=${status.isRunning}, isReady=${status.isReady}, endpoint=${status.otlpEndpoint || 'none'}`);
|
|
1071
1053
|
if (status.isRunning && status.isReady && status.otlpEndpoint) {
|
|
1072
1054
|
logger.info(`[Collector] Found running collector at ${status.otlpEndpoint}`);
|
|
1073
1055
|
return `http://${status.otlpEndpoint}`;
|
|
1074
1056
|
}
|
|
1057
|
+
logger.info('[Collector] No existing collector found, will start new one...');
|
|
1075
1058
|
}
|
|
1076
1059
|
|
|
1077
1060
|
// Collector not running - start it (or wait for existing one to be ready)
|
|
@@ -1089,12 +1072,15 @@ export async function discoverOrStartCollector(
|
|
|
1089
1072
|
logger.info(`[Collector] Waiting for existing collector to become ready at ${endpoint}...`);
|
|
1090
1073
|
} else {
|
|
1091
1074
|
// Start a new collector
|
|
1092
|
-
logger.info('[Collector] Starting OpenTelemetry collector...');
|
|
1075
|
+
logger.info('[Collector] Starting new OpenTelemetry collector...');
|
|
1076
|
+
const startCollectorTime = Date.now();
|
|
1093
1077
|
const startResult = await startCollector(logger, undefined, env);
|
|
1078
|
+
const startCollectorElapsed = Date.now() - startCollectorTime;
|
|
1079
|
+
logger.info(`[Collector] Collector process started in ${startCollectorElapsed}ms, endpoint: ${startResult.endpoint}`);
|
|
1094
1080
|
endpoint = startResult.endpoint;
|
|
1095
1081
|
|
|
1096
1082
|
// Check if collector process exited immediately (crashed)
|
|
1097
|
-
|
|
1083
|
+
logger.info('[Collector] Checking if collector process is still running...');
|
|
1098
1084
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
1099
1085
|
|
|
1100
1086
|
if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
|
|
@@ -1103,6 +1089,7 @@ export async function discoverOrStartCollector(
|
|
|
1103
1089
|
logger.error(`[Collector] ${errorMsg}`);
|
|
1104
1090
|
return null;
|
|
1105
1091
|
}
|
|
1092
|
+
logger.info('[Collector] Collector process is still running, proceeding to readiness check...');
|
|
1106
1093
|
}
|
|
1107
1094
|
|
|
1108
1095
|
// CRITICAL: Wait for collector to be fully ready before returning
|
|
@@ -1112,28 +1099,33 @@ export async function discoverOrStartCollector(
|
|
|
1112
1099
|
const checkInterval = 500; // Check every 500ms
|
|
1113
1100
|
const maxChecks = Math.floor(maxWaitTime / checkInterval);
|
|
1114
1101
|
|
|
1115
|
-
logger.info(
|
|
1102
|
+
logger.info(`[Collector] Waiting for collector to become ready (checking every ${checkInterval}ms, max ${maxWaitTime / 1000}s)...`);
|
|
1103
|
+
const readinessStartTime = Date.now();
|
|
1116
1104
|
|
|
1117
1105
|
for (let i = 0; i < maxChecks; i++) {
|
|
1118
1106
|
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
1107
|
+
const elapsed = Date.now() - readinessStartTime;
|
|
1119
1108
|
|
|
1120
1109
|
// Check if process exited during wait
|
|
1121
1110
|
if (globalCollectorExitCode !== null && globalCollectorExitCode !== 0) {
|
|
1122
1111
|
const errorMsg = `Collector process exited during startup with code ${globalCollectorExitCode}. ${globalCollectorStderr.length > 0 ? `Stderr: ${globalCollectorStderr.join('; ')}` : 'No stderr output.'}`;
|
|
1123
1112
|
globalCollectorInitError = errorMsg;
|
|
1124
|
-
logger.error(`[Collector] ${errorMsg}`);
|
|
1113
|
+
logger.error(`[Collector] ${errorMsg} (after ${elapsed}ms)`);
|
|
1125
1114
|
return null;
|
|
1126
1115
|
}
|
|
1127
1116
|
|
|
1117
|
+
logger.info(`[Collector] Checking collector readiness... (${(elapsed / 1000).toFixed(1)}s elapsed, attempt ${i + 1}/${maxChecks})`);
|
|
1128
1118
|
const newStatus = await isCollectorRunning();
|
|
1119
|
+
logger.info(`[Collector] Collector status: isRunning=${newStatus.isRunning}, isReady=${newStatus.isReady}, pid=${newStatus.pid}, endpoint=${newStatus.otlpEndpoint || 'none'}`);
|
|
1120
|
+
|
|
1129
1121
|
if (newStatus.isRunning && newStatus.isReady) {
|
|
1130
|
-
logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint}`);
|
|
1122
|
+
logger.info(`[Collector] Collector is ready at ${newStatus.otlpEndpoint || endpoint} (ready after ${elapsed}ms)`);
|
|
1131
1123
|
return newStatus.otlpEndpoint ? `http://${newStatus.otlpEndpoint}` : endpoint;
|
|
1132
1124
|
}
|
|
1133
1125
|
|
|
1134
|
-
// Log progress every 2
|
|
1135
|
-
if (i > 0 && i %
|
|
1136
|
-
logger.info(`[Collector] Still waiting for collector to become ready... (${(
|
|
1126
|
+
// Log progress every second (every 2 checks)
|
|
1127
|
+
if (i > 0 && i % 2 === 0) {
|
|
1128
|
+
logger.info(`[Collector] Still waiting for collector to become ready... (${(elapsed / 1000).toFixed(1)}s elapsed)`);
|
|
1137
1129
|
}
|
|
1138
1130
|
}
|
|
1139
1131
|
|
package/src/runtime.ts
CHANGED
|
@@ -87,40 +87,28 @@ export class MicroserviceRuntime {
|
|
|
87
87
|
// Use deasync to wait synchronously (constructor can't be async)
|
|
88
88
|
startupLogger.info('Setting up OpenTelemetry collector (waiting for readiness before enabling Portal logs)...');
|
|
89
89
|
|
|
90
|
+
// Wait for collector to be ready - simple linear async/await
|
|
91
|
+
// Use deasync since constructor can't be async
|
|
90
92
|
let otlpEndpoint: string | null = null;
|
|
91
93
|
let collectorReady = false;
|
|
92
|
-
let collectorError: string | null = null;
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
startCollectorAndWaitForReady(this.env, 180000)
|
|
95
|
+
startCollectorAndWaitForReady(this.env)
|
|
96
96
|
.then((endpoint) => {
|
|
97
97
|
otlpEndpoint = endpoint;
|
|
98
98
|
collectorReady = true;
|
|
99
|
-
if (endpoint) {
|
|
100
|
-
startupLogger.info(`Collector ready at ${endpoint}, creating structured logger for Portal logs...`);
|
|
101
|
-
} else {
|
|
102
|
-
startupLogger.warn('Collector setup did not complete in time, Portal logs will not be available');
|
|
103
|
-
}
|
|
104
99
|
})
|
|
105
100
|
.catch((error) => {
|
|
106
|
-
|
|
101
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
102
|
+
startupLogger.error(`Failed to setup collector: ${errorMsg}`);
|
|
103
|
+
otlpEndpoint = null;
|
|
107
104
|
collectorReady = true;
|
|
108
|
-
startupLogger.error(`Failed to setup collector: ${collectorError}`);
|
|
109
105
|
});
|
|
110
106
|
|
|
111
|
-
// Wait synchronously for collector to be ready (
|
|
112
|
-
|
|
113
|
-
const timeoutMs = 180000; // 3 minutes
|
|
114
|
-
deasync.loopWhile(() => {
|
|
115
|
-
if (Date.now() - startTime >= timeoutMs) {
|
|
116
|
-
return false; // Timeout
|
|
117
|
-
}
|
|
118
|
-
return !collectorReady; // Continue waiting if not ready
|
|
119
|
-
});
|
|
107
|
+
// Wait synchronously for collector to be ready (constructor can't be async)
|
|
108
|
+
deasync.loopWhile(() => !collectorReady);
|
|
120
109
|
|
|
121
110
|
// STEP 4: Create main logger
|
|
122
111
|
// CRITICAL: Only create structured logger (Portal logs) if collector is ready
|
|
123
|
-
// If collector timed out or failed, continue with minimal console logger
|
|
124
112
|
// This ensures Portal logs only appear AFTER collector is ready
|
|
125
113
|
if (otlpEndpoint) {
|
|
126
114
|
// Collector is ready - create structured logger with OTLP support
|