mirror of
https://github.com/soconnor0919/beenpad.git
synced 2026-02-05 08:16:37 -05:00
173 lines
4.6 KiB
JavaScript
173 lines
4.6 KiB
JavaScript
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 };
|