otel.ts
1 import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' 2 import { NodeSDK } from '@opentelemetry/sdk-node' 3 import { log } from '@/lib/server/logger' 4 import { hmrSingleton } from '@/lib/shared-utils' 5 import { resolveOtelConfig } from '@/lib/server/observability/otel-config' 6 7 const TAG = 'otel' 8 9 interface OTelState { 10 started: boolean 11 startPromise: Promise<boolean> | null 12 sdk: NodeSDK | null 13 } 14 15 const otelState = hmrSingleton<OTelState>('__swarmclaw_otel_state__', () => ({ 16 started: false, 17 startPromise: null, 18 sdk: null, 19 })) 20 21 export function isOtelEnabled(): boolean { 22 return resolveOtelConfig() !== null 23 } 24 25 export async function ensureOpenTelemetryStarted(): Promise<boolean> { 26 const config = resolveOtelConfig() 27 if (!config) return false 28 if (otelState.started) return true 29 if (otelState.startPromise) return otelState.startPromise 30 31 otelState.startPromise = (async () => { 32 try { 33 process.env.OTEL_SERVICE_NAME = process.env.OTEL_SERVICE_NAME || config.serviceName 34 const exporter = new OTLPTraceExporter({ 35 url: config.tracesEndpoint, 36 headers: Object.keys(config.headers).length > 0 ? config.headers : undefined, 37 }) 38 const sdk = new NodeSDK({ 39 traceExporter: exporter, 40 }) 41 sdk.start() 42 otelState.sdk = sdk 43 otelState.started = true 44 log.info(TAG, 'OpenTelemetry OTLP tracing enabled', { 45 serviceName: config.serviceName, 46 tracesEndpoint: config.tracesEndpoint, 47 }) 48 return true 49 } catch (err) { 50 otelState.sdk = null 51 otelState.started = false 52 log.error(TAG, 'Failed to initialize OpenTelemetry tracing', err) 53 return false 54 } finally { 55 otelState.startPromise = null 56 } 57 })() 58 59 return otelState.startPromise 60 } 61 62 export async function shutdownOpenTelemetry(): Promise<void> { 63 const sdk = otelState.sdk 64 if (!sdk) { 65 otelState.started = false 66 otelState.startPromise = null 67 return 68 } 69 70 otelState.sdk = null 71 otelState.started = false 72 otelState.startPromise = null 73 74 try { 75 await sdk.shutdown() 76 } catch (err) { 77 log.warn(TAG, 'Failed to flush OpenTelemetry tracing during shutdown', err) 78 } 79 }