class Traces { /** * otel stands for OpenTelemetry */ #otel = null; #sdk = null; #init = null; #noopSpan = createNoopSpan(); #noopContext = createNoopContext(); constructor(options) { if (options.enabled) { const apiInit = import('@opentelemetry/api').then((api) => { this.#otel = { tracer: api.trace.getTracer(options.tracerName || "vitest"), context: api.context, propagation: api.propagation, trace: api.trace, SpanKind: api.SpanKind, SpanStatusCode: api.SpanStatusCode }; }).catch(() => { throw new Error(`"@opentelemetry/api" is not installed locally. Make sure you have setup OpenTelemetry instrumentation: https://vitest.dev/guide/open-telemetry`); }); const sdkInit = (options.sdkPath ? import(options.sdkPath) : Promise.resolve()).catch((cause) => { throw new Error(`Failed to import custom OpenTelemetry SDK script (${options.sdkPath}): ${cause.message}`); }); this.#init = Promise.all([sdkInit, apiInit]).then(([sdk]) => { if (sdk != null) { if (sdk.default != null && typeof sdk.default === "object" && typeof sdk.default.shutdown === "function") this.#sdk = sdk.default; else if (options.watchMode !== true && process.env.VITEST_MODE !== "watch") console.warn(`OpenTelemetry instrumentation module (${options.sdkPath}) does not have a default export with a "shutdown" method. Vitest won't be able to ensure that all traces are processed in time. Try running Vitest in watch mode instead.`); } }).finally(() => { this.#init = null; }); } } isEnabled() { return !!this.#otel; } /** * @internal */ async waitInit() { if (this.#init) await this.#init; return this; } /** * @internal */ startContextSpan(name, currentContext) { if (!this.#otel) return { span: this.#noopSpan, context: this.#noopContext }; const activeContext = currentContext || this.#otel.context.active(); const span = this.#otel.tracer.startSpan(name, {}, activeContext); return { span, context: this.#otel.trace.setSpan(activeContext, span) }; } /** * @internal */ getContextFromCarrier(carrier) { if (!this.#otel) return this.#noopContext; const activeContext = this.#otel.context.active(); if (!carrier) return activeContext; return this.#otel.propagation.extract(activeContext, carrier); } /** * @internal */ getContextCarrier(context) { if (!this.#otel) return; const carrier = {}; this.#otel.propagation.inject(context || this.#otel.context.active(), carrier); return carrier; } #callActiveSpan(span, callback) { const otel = this.#otel; let result; try { result = callback(span); if (result instanceof Promise) return result.catch((error) => { span.recordException({ name: error.name, message: error.message, stack: error.stack }); span.setStatus({ code: otel.SpanStatusCode.ERROR }); throw error; }).finally(() => span.end()); return result; } catch (error) { if (error instanceof Error) { span.recordException({ name: error.name, message: error.message, stack: error.stack }); span.setStatus({ code: otel.SpanStatusCode.ERROR }); } throw error; } finally { // end sync callbcak if (!(result instanceof Promise)) span.end(); } } /** * @internal */ $(name, optionsOrFn, fn) { const callback = typeof optionsOrFn === "function" ? optionsOrFn : fn; if (!this.#otel) return callback(this.#noopSpan); const otel = this.#otel; const options = typeof optionsOrFn === "function" ? {} : optionsOrFn; const context = options.context; if (context) return otel.tracer.startActiveSpan(name, options, context, (span) => this.#callActiveSpan(span, callback)); return otel.tracer.startActiveSpan(name, options, (span) => this.#callActiveSpan(span, callback)); } /** * @internal */ startSpan(name, options, context) { if (!this.#otel) return this.#noopSpan; const { tracer } = this.#otel; return tracer.startSpan(name, options, context); } /** * @internal */ async finish() { await this.#sdk?.shutdown(); } } function noopSpan() { return this; } function createNoopSpan() { return { setAttribute: noopSpan, setStatus: noopSpan, addEvent: noopSpan, addLink: noopSpan, addLinks: noopSpan, setAttributes: noopSpan, updateName: noopSpan, end: () => {}, isRecording: () => false, recordException: noopSpan, spanContext() { return { spanId: "", traceFlags: 0, traceId: "" }; } }; } function noopContext() { return this; } function createNoopContext() { return { getValue: noopContext, setValue: noopContext, deleteValue: noopContext }; } export { Traces as T };