UNPKG

autotel

Version:
254 lines (251 loc) 9.18 kB
import { trace } from "@opentelemetry/api"; //#region src/tracer-provider.ts /** * Isolated tracer provider support for Autotel * * Allows Autotel to use a separate TracerProvider instance, avoiding conflicts * with other OpenTelemetry instrumentation in the application. * * **Use Case:** Library authors who want to use Autotel without interfering * with the application's global OpenTelemetry setup. * * **Limitation:** While this isolates span processing and export, OpenTelemetry * context (trace IDs, parent spans) is still shared globally. Spans created with * the isolated provider may inherit trace context from global spans. */ /** * Symbol for storing isolated tracer provider in global scope * Using Symbol.for() ensures the same symbol across module boundaries */ const AUTOTEL_GLOBAL_SYMBOL = Symbol.for("autotel"); /** * Create initial state */ function createState() { return { isolatedTracerProvider: null }; } /** * Get the global state, creating it if it doesn't exist * Handles edge cases like missing globalThis */ function getGlobalState() { const initialState = createState(); try { const g = globalThis; if (typeof g !== "object" || g === null) { console.warn("[autotel] globalThis is not available, using fallback state"); return initialState; } if (!g[AUTOTEL_GLOBAL_SYMBOL]) Object.defineProperty(g, AUTOTEL_GLOBAL_SYMBOL, { value: initialState, writable: false, configurable: false, enumerable: false }); return g[AUTOTEL_GLOBAL_SYMBOL]; } catch (error) { if (error instanceof Error) console.error(`[autotel] Failed to access global state: ${error.message}`); else console.error(`[autotel] Failed to access global state: ${String(error)}`); return initialState; } } /** * Sets an isolated TracerProvider for Autotel tracing operations. * * This allows Autotel to use its own TracerProvider instance, separate from * the global OpenTelemetry TracerProvider. This is useful for avoiding conflicts * with other OpenTelemetry instrumentation in the application. * * **Limitation: Span Context Sharing** * * While this function isolates span processing and export, it does NOT provide * complete trace isolation. OpenTelemetry context (trace IDs, parent spans) is * still shared between the global and isolated providers. This means: * * - Spans created with the isolated provider inherit trace IDs from global spans * - Spans created with the isolated provider inherit parent relationships from global spans * - This can result in spans from different providers being part of the same logical trace * * **Why this happens:** * OpenTelemetry uses a global context propagation mechanism that operates at the * JavaScript runtime level, independent of individual TracerProvider instances. * The context (containing trace ID, span ID) flows through async boundaries and * is inherited by all spans created within that context, regardless of which * TracerProvider creates them. * * **When to use this:** * - Library code that ships with embedded Autotel * - SDKs that want observability without requiring users to set up OpenTelemetry * - Applications that need separate span processing for different subsystems * - Testing scenarios where you want to isolate trace collection * * @param provider - The TracerProvider instance to use, or null to clear the isolated provider * * @example Library with embedded Autotel * ```typescript * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' * import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base' * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' * import { setAutolem etryTracerProvider } from 'autotel/tracer-provider' * * // Create provider with span processors in constructor * const exporter = new OTLPTraceExporter({ * url: 'https://your-backend.com/v1/traces' * }) * * const provider = new NodeTracerProvider() * provider.addSpanProcessor(new BatchSpanProcessor(exporter)) * * // Set as Autotel's isolated provider (doesn't call provider.register()) * setAutotelTracerProvider(provider) * * // Now all Autotel trace() calls use this provider * // But won't interfere with the application's global OpenTelemetry setup * ``` * * @example Testing with isolated provider * ```typescript * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' * import { InMemorySpanExporter } from '@opentelemetry/sdk-trace-base' * import { setAutotelTracerProvider } from 'autotel/tracer-provider' * * // Test setup * const exporter = new InMemorySpanExporter() * const provider = new NodeTracerProvider() * provider.addSpanProcessor(new SimpleSpanProcessor(exporter)) * * setAutotelTracerProvider(provider) * * // Run tests... * const spans = exporter.getFinishedSpans() * * // Cleanup * setAutotelTracerProvider(null) * ``` * * @example Multiple subsystems with different exporters * ```typescript * import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' * import { setAutotelTracerProvider } from 'autotel/tracer-provider' * * // Payment subsystem - send to payment team's backend * const paymentProvider = new NodeTracerProvider() * paymentProvider.addSpanProcessor(new BatchSpanProcessor( * new OTLPTraceExporter({ url: 'https://payment-team-backend.com/v1/traces' }) * )) * * // In payment module initialization * setAutotelTracerProvider(paymentProvider) * ``` * * @public */ function setAutotelTracerProvider(provider) { getGlobalState().isolatedTracerProvider = provider; } /** * Gets the TracerProvider for Autotel tracing operations. * * Returns the isolated TracerProvider if one has been set via setAutotelTracerProvider(), * otherwise falls back to the global OpenTelemetry TracerProvider. * * This function is used internally by Autotel's trace functions. Most users * will not need to call this directly. * * @returns The TracerProvider instance to use for Autotel tracing * * @example Getting the current provider * ```typescript * import { getAutotelTracerProvider } from 'autotel/tracer-provider' * * const provider = getAutotelTracerProvider() * const tracer = provider.getTracer('my-service', '1.0.0') * ``` * * @example Checking if isolated provider is active * ```typescript * import { getAutotelTracerProvider, setAutotelTracerProvider } from 'autotel/tracer-provider' * import { trace } from '@opentelemetry/api' * * const currentProvider = getAutotelTracerProvider() * const globalProvider = trace.getTracerProvider() * * if (currentProvider === globalProvider) { * console.log('Using global provider') * } else { * console.log('Using isolated provider') * } * ``` * * @public */ function getAutotelTracerProvider() { const { isolatedTracerProvider } = getGlobalState(); if (isolatedTracerProvider) return isolatedTracerProvider; return trace.getTracerProvider(); } function isForceFlushable(value) { return typeof value === "object" && value !== null && typeof value.forceFlush === "function"; } /** * Resolve a tracer provider that can be `forceFlush()`ed, so `flush()` and * flush-on-shutdown actually export pending spans. * * `NodeSDK.getTracerProvider()` returns `undefined` on `@opentelemetry/sdk-node` * 0.220+ (OpenTelemetry 2.x), so code that force-flushes only via the SDK * handle silently exports nothing. This falls back to the globally registered * provider and unwraps the API's `ProxyTracerProvider` to reach the real * delegate, which is where `forceFlush` actually lives. * * @param sdk - Optional NodeSDK handle to try first. * @returns Something with `forceFlush()`, or `undefined` if none is registered. */ function getForceFlushableProvider(sdk) { const candidates = []; const sdkAny = sdk; if (sdkAny && typeof sdkAny.getTracerProvider === "function") candidates.push(sdkAny.getTracerProvider()); const globalProvider = trace.getTracerProvider(); candidates.push(globalProvider); const proxy = globalProvider; if (proxy && typeof proxy.getDelegate === "function") candidates.push(proxy.getDelegate()); return candidates.find(isForceFlushable); } /** * Gets the OpenTelemetry tracer instance for Autotel. * * This function returns a tracer specifically configured for Autotel * with the correct tracer name and version. Used internally by all * Autotel tracing functions to ensure consistent trace creation. * * Uses the isolated provider if set, otherwise uses the global provider. * * @param name - Tracer name (default: 'autotel') * @param version - Optional version string * @returns The Autotel OpenTelemetry tracer instance * * @example Basic usage * ```typescript * import { getAutotelTracer } from 'autotel/tracer-provider' * * const tracer = getAutotelTracer() * const span = tracer.startSpan('my-operation') * // ... use span * span.end() * ``` * * @example Custom tracer name * ```typescript * import { getAutotelTracer } from 'autotel/tracer-provider' * * const tracer = getAutotelTracer('my-library', '2.1.0') * ``` * * @public */ function getAutotelTracer(name = "autotel", version) { return getAutotelTracerProvider().getTracer(name, version); } //#endregion export { getAutotelTracer, getAutotelTracerProvider, getForceFlushableProvider, setAutotelTracerProvider }; //# sourceMappingURL=tracer-provider.js.map