@omen.foundation/node-microservice-runtime 0.1.51 → 0.1.52
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 +45 -13
- package/dist/collector-manager.d.ts +10 -4
- package/dist/collector-manager.d.ts.map +1 -1
- package/dist/collector-manager.js +65 -19
- package/dist/collector-manager.js.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.cjs +23 -69
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +37 -100
- package/dist/logger.js.map +1 -1
- package/dist/runtime.cjs +2 -9
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +10 -23
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/collector-manager.ts +70 -18
- package/src/index.ts +1 -0
- package/src/logger.ts +50 -121
- package/src/runtime.ts +10 -25
package/src/logger.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
|
|
|
9
9
|
import { resourceFromAttributes, defaultResource } from '@opentelemetry/resources';
|
|
10
10
|
import { discoverOrStartCollector } from './collector-manager.js';
|
|
11
11
|
import type { Logger as PinoLogger } from 'pino';
|
|
12
|
-
import deasync from 'deasync';
|
|
13
12
|
|
|
14
13
|
// Helper to get require function that works in both CJS and ESM
|
|
15
14
|
declare const require: any;
|
|
@@ -535,113 +534,6 @@ function createBeamableLogFormatter(
|
|
|
535
534
|
});
|
|
536
535
|
}
|
|
537
536
|
|
|
538
|
-
/**
|
|
539
|
-
* Synchronously initializes OTLP logging with a timeout.
|
|
540
|
-
* This ensures OTLP is ready before any logs are emitted.
|
|
541
|
-
* Similar to C# microservices which configure logging early in startup.
|
|
542
|
-
*
|
|
543
|
-
* Uses a blocking wait mechanism to ensure initialization completes before returning.
|
|
544
|
-
*/
|
|
545
|
-
function initializeOtlpSync(
|
|
546
|
-
serviceName?: string,
|
|
547
|
-
qualifiedServiceName?: string,
|
|
548
|
-
env?: EnvironmentConfig,
|
|
549
|
-
timeoutMs: number = 60000 // 60 seconds to allow collector download, startup, and readiness check
|
|
550
|
-
): LoggerProvider | null {
|
|
551
|
-
// Match C# logic: (this.InDocker() || UseLocalOtel) && !BEAM_DISABLE_STANDARD_OTEL
|
|
552
|
-
const isInDocker = process.env.IS_LOCAL !== '1' && process.env.IS_LOCAL !== 'true';
|
|
553
|
-
const useLocalOtel = !!process.env.BEAM_LOCAL_OTEL;
|
|
554
|
-
const standardOtelEnabled = (isInDocker || useLocalOtel) && !process.env.BEAM_DISABLE_STANDARD_OTEL;
|
|
555
|
-
const hasExplicitEndpoint = !!process.env.BEAM_OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
556
|
-
|
|
557
|
-
// If OTLP is not needed, return immediately
|
|
558
|
-
if (!standardOtelEnabled && !hasExplicitEndpoint) {
|
|
559
|
-
return null;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Create a minimal console logger for initialization messages
|
|
563
|
-
const initLogger = pino({
|
|
564
|
-
name: 'beamable-otlp-init',
|
|
565
|
-
level: 'info',
|
|
566
|
-
}, process.stdout);
|
|
567
|
-
|
|
568
|
-
// If collector was already set up via setupCollectorBeforeLogging,
|
|
569
|
-
// discoverOrStartCollector will find it and use a shorter timeout.
|
|
570
|
-
// If not, it will start it (with full timeout).
|
|
571
|
-
|
|
572
|
-
// Use deasync to wait synchronously for the async initialization
|
|
573
|
-
// This allows the event loop to process while we wait, enabling async operations
|
|
574
|
-
// (like collector download) to complete
|
|
575
|
-
let provider: LoggerProvider | null = null;
|
|
576
|
-
let completed = false;
|
|
577
|
-
let initError: Error | null = null;
|
|
578
|
-
|
|
579
|
-
// Start initialization promise (callbacks set flags for deasync.loopWhile to check)
|
|
580
|
-
initializeOtlpLogging(
|
|
581
|
-
serviceName,
|
|
582
|
-
qualifiedServiceName,
|
|
583
|
-
env,
|
|
584
|
-
initLogger
|
|
585
|
-
).then((result) => {
|
|
586
|
-
provider = result;
|
|
587
|
-
completed = true;
|
|
588
|
-
if (result) {
|
|
589
|
-
initLogger.info('[OTLP] OpenTelemetry logging initialized successfully');
|
|
590
|
-
}
|
|
591
|
-
return result;
|
|
592
|
-
}).catch((error) => {
|
|
593
|
-
initError = error instanceof Error ? error : new Error(String(error));
|
|
594
|
-
completed = true;
|
|
595
|
-
initLogger.error(`[OTLP] Failed to initialize: ${initError.message}`);
|
|
596
|
-
return null;
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
// Set timeout to prevent infinite wait
|
|
600
|
-
const startTime = Date.now();
|
|
601
|
-
const timeoutId = setTimeout(() => {
|
|
602
|
-
if (!completed) {
|
|
603
|
-
initLogger.warn(`[OTLP] Initialization timeout after ${timeoutMs}ms, continuing without OTLP`);
|
|
604
|
-
completed = true;
|
|
605
|
-
}
|
|
606
|
-
}, timeoutMs);
|
|
607
|
-
|
|
608
|
-
// Wait synchronously for the promise to resolve using deasync
|
|
609
|
-
// deasync.loopWhile() allows event loop to process while we wait
|
|
610
|
-
// This enables async operations (like collector download) to run and complete
|
|
611
|
-
// CRITICAL: This must complete before the logger is created to capture all startup logs
|
|
612
|
-
try {
|
|
613
|
-
// Use deasync to wait for completion, with timeout check
|
|
614
|
-
// loopWhile returns false to stop waiting, true to continue
|
|
615
|
-
deasync.loopWhile(() => {
|
|
616
|
-
// Check if we've exceeded timeout
|
|
617
|
-
const elapsed = Date.now() - startTime;
|
|
618
|
-
if (elapsed >= timeoutMs) {
|
|
619
|
-
initLogger.warn(`[OTLP] Synchronous wait timeout after ${elapsed}ms`);
|
|
620
|
-
return false; // Stop waiting
|
|
621
|
-
}
|
|
622
|
-
// Continue waiting if not completed
|
|
623
|
-
// This allows the event loop to process async operations
|
|
624
|
-
return !completed;
|
|
625
|
-
});
|
|
626
|
-
} catch (error) {
|
|
627
|
-
// If deasync fails, log and continue
|
|
628
|
-
initLogger.error(`[OTLP] Error during synchronous wait: ${error instanceof Error ? error.message : String(error)}`);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
clearTimeout(timeoutId);
|
|
632
|
-
|
|
633
|
-
// Verify we got a provider (collector is ready)
|
|
634
|
-
if (completed && provider) {
|
|
635
|
-
initLogger.info('[OTLP] Initialization completed successfully, collector is ready');
|
|
636
|
-
} else if (!completed) {
|
|
637
|
-
initLogger.warn('[OTLP] Initialization did not complete in time, logs may not be sent via OTLP initially');
|
|
638
|
-
initLogger.warn('[OTLP] Service will continue without OTLP telemetry - requests will still be serviced');
|
|
639
|
-
} else {
|
|
640
|
-
initLogger.warn('[OTLP] Initialization completed but no provider returned, OTLP telemetry disabled');
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
return provider;
|
|
644
|
-
}
|
|
645
537
|
|
|
646
538
|
export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptions = {}): Logger {
|
|
647
539
|
const configuredDestination = options.destinationPath ?? process.env.LOG_PATH;
|
|
@@ -650,7 +542,13 @@ export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptio
|
|
|
650
542
|
// Initialize OTLP synchronously BEFORE creating the logger
|
|
651
543
|
// If otlpEndpoint is provided (collector already set up), create provider directly
|
|
652
544
|
// Otherwise, try to discover/start collector (with timeout)
|
|
653
|
-
|
|
545
|
+
// Check if standard OTLP is enabled (needed for the else branch)
|
|
546
|
+
const isInDocker = process.env.IS_LOCAL !== '1' && process.env.IS_LOCAL !== 'true';
|
|
547
|
+
const useLocalOtel = !!process.env.BEAM_LOCAL_OTEL;
|
|
548
|
+
const standardOtelEnabled = (isInDocker || useLocalOtel) && !process.env.BEAM_DISABLE_STANDARD_OTEL;
|
|
549
|
+
|
|
550
|
+
// Shared reference for OTLP logger provider (create before async operations)
|
|
551
|
+
const otlpProviderRef: { provider: LoggerProvider | null } = { provider: null };
|
|
654
552
|
|
|
655
553
|
if (options.otlpEndpoint) {
|
|
656
554
|
// Collector is already set up, create OTLP provider directly without discovery/startup
|
|
@@ -699,7 +597,7 @@ export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptio
|
|
|
699
597
|
return !completed;
|
|
700
598
|
});
|
|
701
599
|
|
|
702
|
-
|
|
600
|
+
otlpProviderRef.provider = provider;
|
|
703
601
|
|
|
704
602
|
// Restore original endpoint if it existed
|
|
705
603
|
if (originalEndpoint !== undefined) {
|
|
@@ -708,18 +606,49 @@ export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptio
|
|
|
708
606
|
delete process.env.BEAM_OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
709
607
|
}
|
|
710
608
|
} else {
|
|
711
|
-
// No endpoint provided -
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
609
|
+
// No endpoint provided - collector is starting asynchronously in background
|
|
610
|
+
// Don't block here - logger works immediately via stdout, OTLP will connect when collector is ready
|
|
611
|
+
otlpProviderRef.provider = null; // Start without OTLP - will connect when collector is ready
|
|
612
|
+
|
|
613
|
+
// Start async discovery in background (non-blocking)
|
|
614
|
+
// This allows the service to start immediately while collector is downloading/starting
|
|
615
|
+
if (standardOtelEnabled) {
|
|
616
|
+
// Try to discover collector asynchronously (won't block service startup)
|
|
617
|
+
// If collector is already running, we'll connect immediately
|
|
618
|
+
// If not, we'll retry periodically in the background
|
|
619
|
+
(async () => {
|
|
620
|
+
// Give collector a moment to start (if it's just starting)
|
|
621
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
622
|
+
|
|
623
|
+
// Try to discover collector (quick check, non-blocking)
|
|
624
|
+
try {
|
|
625
|
+
const discoveredEndpoint = await discoverOrStartCollector(
|
|
626
|
+
pino({ name: 'beamable-otlp-bg', level: 'info' }, process.stdout),
|
|
627
|
+
standardOtelEnabled,
|
|
628
|
+
env
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
if (discoveredEndpoint) {
|
|
632
|
+
// Collector is ready - initialize OTLP logging now
|
|
633
|
+
const newProvider = await initializeOtlpLogging(
|
|
634
|
+
options.serviceName,
|
|
635
|
+
options.qualifiedServiceName,
|
|
636
|
+
env,
|
|
637
|
+
pino({ name: 'beamable-otlp-bg', level: 'info' }, process.stdout)
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
if (newProvider) {
|
|
641
|
+
// Update the provider reference so future logs use OTLP
|
|
642
|
+
otlpProviderRef.provider = newProvider;
|
|
643
|
+
console.error(`[OTLP] Connected to collector at ${discoveredEndpoint} (background connection)`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
} catch (error) {
|
|
647
|
+
// Silently fail - collector will connect later if it starts
|
|
648
|
+
}
|
|
649
|
+
})(); // Fire and forget - don't await
|
|
650
|
+
}
|
|
719
651
|
}
|
|
720
|
-
|
|
721
|
-
// Shared reference for OTLP logger provider
|
|
722
|
-
const otlpProviderRef: { provider: LoggerProvider | null } = { provider: otlpProvider };
|
|
723
652
|
|
|
724
653
|
const pinoOptions: LoggerOptions = {
|
|
725
654
|
name: options.name ?? 'beamable-node-runtime',
|
package/src/runtime.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { GatewayRequester } from './requester.js';
|
|
|
4
4
|
import { AuthManager } from './auth.js';
|
|
5
5
|
import { createLogger } from './logger.js';
|
|
6
6
|
import { loadEnvironmentConfig } from './env.js';
|
|
7
|
-
import {
|
|
7
|
+
import { startCollectorAsync } from './collector-manager.js';
|
|
8
8
|
import pino from 'pino';
|
|
9
9
|
import { listRegisteredServices, getServiceOptions, getConfigureServicesHandlers, getInitializeServicesHandlers } from './decorators.js';
|
|
10
10
|
import { generateOpenApiDocument } from './docs.js';
|
|
@@ -80,35 +80,20 @@ export class MicroserviceRuntime {
|
|
|
80
80
|
const primaryService = registered[0];
|
|
81
81
|
const qualifiedServiceName = `micro_${primaryService.qualifiedName}`;
|
|
82
82
|
|
|
83
|
-
// STEP 3:
|
|
84
|
-
// This
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// - Downloading collector binary (~12MB, can take 30-60+ seconds on slow networks)
|
|
89
|
-
// - Downloading collector config (~1 second)
|
|
90
|
-
// - Starting collector process (~1 second)
|
|
91
|
-
// - Waiting for collector to be ready (up to 60 seconds)
|
|
92
|
-
// Total: ~90-125 seconds minimum, so use 180 seconds (3 minutes) for safety
|
|
93
|
-
// Real-world testing shows downloads can take 2+ minutes on slow networks
|
|
94
|
-
const otlpEndpoint = setupCollectorBeforeLogging(
|
|
95
|
-
this.env,
|
|
96
|
-
180000 // 180 second timeout (3 minutes) to allow for slow downloads + startup + readiness
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
if (otlpEndpoint) {
|
|
100
|
-
startupLogger.info(`Collector ready at ${otlpEndpoint}, creating main logger...`);
|
|
101
|
-
} else {
|
|
102
|
-
startupLogger.warn('Collector setup did not complete, continuing without OTLP logging');
|
|
103
|
-
}
|
|
83
|
+
// STEP 3: Start collector asynchronously in background (NON-BLOCKING)
|
|
84
|
+
// This allows the service to start immediately while collector downloads/starts
|
|
85
|
+
// Logs will work immediately (stdout), OTLP will connect when collector is ready
|
|
86
|
+
startupLogger.info('Starting OpenTelemetry collector setup in background (non-blocking)...');
|
|
87
|
+
startCollectorAsync(this.env);
|
|
104
88
|
|
|
105
|
-
// STEP 4: Create main logger (
|
|
106
|
-
//
|
|
89
|
+
// STEP 4: Create main logger immediately (service can start serving requests)
|
|
90
|
+
// Logger works immediately via stdout, OTLP will connect when collector is ready
|
|
91
|
+
// The logger will automatically start using OTLP once the collector is available
|
|
107
92
|
this.logger = createLogger(this.env, {
|
|
108
93
|
name: 'beamable-node-microservice',
|
|
109
94
|
serviceName: primaryService.name,
|
|
110
95
|
qualifiedServiceName: qualifiedServiceName,
|
|
111
|
-
|
|
96
|
+
// Don't pass otlpEndpoint - let it discover/connect when collector is ready
|
|
112
97
|
});
|
|
113
98
|
this.serviceManager = new BeamableServiceManager(this.env, this.logger);
|
|
114
99
|
|