import type { Context, Span, SpanOptions, Tracer } from "@opentelemetry/api"; import { context as apiContext } from "@opentelemetry/api"; import { getAttributesFromContext } from "../contextAttributes"; import { OISpan } from "./OISpan"; import { generateTraceConfig } from "./traceConfig"; import type { OpenInferenceActiveSpanCallback, TraceConfig, TraceConfigOptions } from "./types"; /** * Formats the params for the startActiveSpan method * The method has multiple overloads, so we need to format the arguments * Taken from @see https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-sdk-trace-base/src/Tracer.ts#L220C3-L235C6 * */ function formatStartActiveSpanParams( arg2?: SpanOptions | F, arg3?: Context | F, arg4?: F, ) { let opts: SpanOptions | undefined; let ctx: Context | undefined; let fn: F; if (typeof arg2 === "function") { fn = arg2; } else if (typeof arg3 === "function") { opts = arg2; fn = arg3; } else { opts = arg2; ctx = arg3; fn = arg4 as F; } opts = opts ?? {}; ctx = ctx ?? apiContext.active(); return { opts, ctx, fn }; } /** * A wrapper around the OpenTelemetry {@link Tracer} interface that masks sensitive information based on the passed in {@link TraceConfig}. */ export class OITracer implements Tracer { private readonly tracer: Tracer; private readonly config: TraceConfig; /** * @param params - The parameters for the OITracer constructor * @param params.tracer - The OpenTelemetry {@link Tracer} to wrap * @param params.traceConfig - The {@link TraceConfigOptions} to set to control the behavior of the tracer */ constructor({ tracer, traceConfig }: { tracer: Tracer; traceConfig?: TraceConfigOptions }) { this.tracer = tracer; this.config = generateTraceConfig(traceConfig); } startActiveSpan unknown>(name: string, fn: F): ReturnType; startActiveSpan unknown>( name: string, options: SpanOptions, fn: F, ): ReturnType; startActiveSpan unknown>( name: string, options: SpanOptions, context: Context, fn: F, ): ReturnType; startActiveSpan ReturnType>( name: string, arg2?: F | SpanOptions, arg3?: F | Context, arg4?: F, ): ReturnType | undefined { const formattedArgs = formatStartActiveSpanParams(arg2, arg3, arg4); if (formattedArgs == null) { return; } const { opts, ctx, fn } = formattedArgs; const { attributes } = opts ?? {}; const contextAttributes = getAttributesFromContext(ctx); const mergedAttributes = { ...contextAttributes, ...attributes }; return this.tracer.startActiveSpan( name, { ...opts, attributes: undefined }, ctx, (span: Span) => { const openInferenceSpan = new OISpan({ span, config: this.config, }); openInferenceSpan.setAttributes(mergedAttributes); return fn(openInferenceSpan); }, ); } startSpan(name: string, options?: SpanOptions, context?: Context): OISpan { const attributes = options?.attributes; const ctx = context ?? apiContext.active(); const contextAttributes = getAttributesFromContext(ctx); const mergedAttributes = { ...contextAttributes, ...attributes }; const span = new OISpan({ span: this.tracer.startSpan(name, { ...options, attributes: undefined }, ctx), config: this.config, }); span.setAttributes(mergedAttributes); return span; } }