/ src / lib / server / observability / otel.ts
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  }