import { mkdir, appendFile } from 'node:fs/promises'; import { dirname } from 'node:path'; import type { NodeSDK } from '@opentelemetry/sdk-node'; import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import type { ExportResult } from '@opentelemetry/core'; const EXPORT_RESULT_SUCCESS = 0; const EXPORT_RESULT_FAILED = 1; declare global { var __deeplineTracingInitPromise: Promise | undefined; var __deeplineTracingSdk: NodeSDK | undefined; var __deeplineTracingShutdownPromise: Promise | undefined; } class JsonlFileSpanExporter implements SpanExporter { constructor(private readonly filePath: string) {} async export( spans: ReadableSpan[], resultCallback: (result: ExportResult) => void, ): Promise { try { await mkdir(dirname(this.filePath), { recursive: true }); const lines = spans .map((span) => JSON.stringify({ traceId: span.spanContext().traceId, spanId: span.spanContext().spanId, parentSpanId: span.parentSpanContext?.spanId ?? null, name: span.name, kind: span.kind, startTime: span.startTime, endTime: span.endTime, attributes: span.attributes, status: span.status, resource: span.resource.attributes, }), ) .join('\n'); if (lines.length > 0) { await appendFile(this.filePath, `${lines}\n`, 'utf-8'); } resultCallback({ code: EXPORT_RESULT_SUCCESS }); } catch (error) { resultCallback({ code: EXPORT_RESULT_FAILED, error: error instanceof Error ? error : new Error(String(error)), }); } } async shutdown(): Promise {} async forceFlush(): Promise {} } async function resolveTraceExporters(): Promise { const exporters: SpanExporter[] = []; const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim() || process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim() || ''; if (otlpEndpoint) { const { OTLPTraceExporter } = await import('@opentelemetry/exporter-trace-otlp-http'); exporters.push(new OTLPTraceExporter()); } const traceFilePath = process.env.DEEPLINE_TRACE_FILE?.trim() || ''; if (traceFilePath) { exporters.push(new JsonlFileSpanExporter(traceFilePath)); } return exporters; } async function startNodeTracing(serviceName: string): Promise { const exporters = await resolveTraceExporters(); if (exporters.length === 0) { return false; } if (!process.env.OTEL_SERVICE_NAME) { process.env.OTEL_SERVICE_NAME = serviceName; } const [{ NodeSDK }, { BatchSpanProcessor }] = await Promise.all([ import('@opentelemetry/sdk-node'), import('@opentelemetry/sdk-trace-base'), ]); const sdk = new NodeSDK({ instrumentations: [], spanProcessors: exporters.map( (exporter) => new BatchSpanProcessor(exporter), ), }); await sdk.start(); globalThis.__deeplineTracingSdk = sdk; return true; } export async function ensureNodeTracing(serviceName: string): Promise { globalThis.__deeplineTracingInitPromise ??= startNodeTracing(serviceName); return await globalThis.__deeplineTracingInitPromise; } export async function shutdownNodeTracing(): Promise { const initialized = await (globalThis.__deeplineTracingInitPromise ?? Promise.resolve(false)); if (!initialized) { return; } const sdk = globalThis.__deeplineTracingSdk; if (!sdk) { return; } globalThis.__deeplineTracingShutdownPromise ??= sdk.shutdown().finally(() => { globalThis.__deeplineTracingSdk = undefined; globalThis.__deeplineTracingInitPromise = undefined; }); await globalThis.__deeplineTracingShutdownPromise; }