UNPKG

@mwcp/otel

Version:
221 lines (184 loc) 6.57 kB
import assert from 'node:assert' import type { Application, Context, GrpcContext } from '@mwcp/share' import type { Attributes, Context as TraceContext, Span, SpanOptions, TimeInput, } from '@opentelemetry/api' import { context as contextFunc } from '@opentelemetry/api' import { initSpanStatusOptions } from '../config.js' import type { AddEventOptions, SpanStatusOptions, TraceScopeType } from '../types.js' import { getSpan } from '../util.js' import { type TraceInfo, TraceServiceBase } from './trace.service.base.js' import type { EndSpanOptions, StartScopeActiveSpanOptions } from './trace.service.types.js' export class TraceServiceSpan extends TraceServiceBase { getTraceId(): string { return this.otel.getTraceId() ?? '' } getRootSpan(scope: TraceScopeType): Span | undefined { const rootSpan = this.otel.getRootSpan(scope) return rootSpan } /** * Get span from the given scope, if not exists, get span from the request context or application. */ getActiveSpan(): Span | undefined { return this.getActiveTraceInfo().span } getActiveTraceInfo(): TraceInfo { const traceCtx = this.getActiveContext() const span = getSpan(traceCtx) assert(span, 'getActiveTraceInfo() span should not be null') return { span, traceContext: traceCtx } } /** * Starts a new {@link Span}. Start the span **without** setting it on context. * This method do NOT modify the current Context. * @default scope is `request ctx` */ startSpan( name: string, options?: SpanOptions, traceContext?: TraceContext, scope?: TraceScopeType, ): TraceInfo { let traceCtx = traceContext if (! traceCtx) { const scope2 = scope ?? this.getWebContext() assert(scope2, 'startSpan() scope should not be null') traceCtx = this.getActiveContext() } const ret = this.otel.startSpan(name, options, traceCtx) return ret } /** * Starts a new {@link Span}. Start the span **without** setting it on context. * @default scope is `request ctx` */ startScopeSpan(options: StartScopeActiveSpanOptions): TraceInfo { const parentCtx = options.traceContext ?? this.getActiveContext() const ret = this.otel.startSpanContext(options.name, options.spanOptions, parentCtx) // const cb = (span: Span, ctx: TraceContext) => { return { span, traceContext: ctx } } // const ret: TraceInfo = this.otel.startActiveSpan(options.name, cb, options.spanOptions, parentCtx) assert(ret, 'startScopeActiveSpan() ret should not be null') const scope = options.scope ?? this.getWebContext() if (scope) { this.setActiveContext(ret.traceContext, scope) } return ret } /** * Starts a new {@link Span} and calls the given function passing it the created span as first argument. * Additionally the new span gets set in context and this context is activated * for the duration of the function call. * * @default scope is `this.ctx` * @CAUTION: the span returned by this method is NOT ended automatically, * you must to call `this.endSpan()` manually instead of span.edn() directly. */ startActiveSpan<F extends (...args: [Span, TraceContext]) => ReturnType<F>>( name: string, callback: F, options?: SpanOptions, traceContext?: TraceContext, scope?: TraceScopeType, ): ReturnType<F> { const scope2 = scope ?? this.getWebContext() const { span, traceContext: traceCtx } = this.startScopeSpan({ name, spanOptions: options, traceContext, scope: scope2, }) return contextFunc.with(traceCtx, callback, void 0, span, traceCtx) } /** * Do following steps: * - ends the given span * - set span with error if error passed in params * - set span status * - call span.end(), except span is root span */ endSpan(options: EndSpanOptions): void { const { span, spanStatusOptions, endTime, scope } = options if (! this.config.enable) { return } const scope2 = scope ?? this.getWebContext() const rootSpan = scope2 ? this.getRootSpan(scope2) : void 0 const statusOpts = spanStatusOptions ?? initSpanStatusOptions const isRootSpan = scope2 ? this.otel.spanIsRootSpan(scope2, span) : true // true? if (isRootSpan) { this.otel.endRootSpan(span, spanStatusOptions, endTime) } else { this.otel.endSpan(rootSpan, span, statusOpts, endTime) } } endRootSpan( spanStatusOptions: SpanStatusOptions = initSpanStatusOptions, endTime?: TimeInput, scope?: TraceScopeType, ): void { if (! this.config.enable) { return } assert(scope, 'scope should not be null') const rootSpan = this.getRootSpan(scope) assert(rootSpan, 'traceService.endRootSpan(): rootSpan should not be null') this.otel.endRootSpan(rootSpan, spanStatusOptions, endTime) } /** * Sets the span with the error passed in params, note span not ended. */ setSpanWithError( span: Span, error: Error | undefined, eventName?: string, scope?: TraceScopeType, ): void { if (! this.config.enable) { return } const scope2 = scope ?? this.getWebContext() const rootSpan = scope2 ? this.getRootSpan(scope2) : void 0 this.otel.setSpanWithError(rootSpan, span, error, eventName) } /** * Sets the span with the error passed in params, note span not ended. */ setRootSpanWithError( error: Error | undefined, eventName?: string, scope?: Application | Context | GrpcContext, ): void { if (! this.config.enable) { return } const scope2 = scope ?? this.getWebContext() const rootSpan = scope2 ? this.getRootSpan(scope2) : void 0 if (rootSpan) { this.otel.setSpanWithError(rootSpan, rootSpan, error, eventName) } } /** * Adds an event to the given span. */ addEvent( span: Span, input: Attributes, options?: AddEventOptions, ): void { if (! this.config.enable) { return } this.otel.addEvent(span, input, options) } /** * Sets the attributes to the given span. */ setAttributes(span: Span | undefined, input: Attributes): void { if (! this.config.enable) { return } let target = span if (! target) { const webCtx = this.getWebContext() assert(webCtx, 'setAttributes() webCtx should not be null, maybe this calling is not in a request context') const rootSpan = this.getRootSpan(webCtx) assert(rootSpan, 'rootSpan should not be null') target = rootSpan } this.otel.setAttributes(target, input) } }