UNPKG

@mwcp/otel

Version:
245 lines (205 loc) 6.41 kB
import assert from 'node:assert' import type { DecoratorExecutorParamBase } from '@mwcp/share' import { context } from '@opentelemetry/api' import type { Span } from '@opentelemetry/api' import { isArrowFunction } from '@waiting/shared-core' import type { SpanStatusOptions, TraceContext } from '##/lib/types.js' import type { DecoratorContext, DecoratorExecutorParam, GenDecoratorExecutorOptions, KeyGenerator, TraceDecoratorOptions, } from './trace.service/index.trace.service.js' import type { TraceServiceSpan } from './trace.service/trace.service.span.js' import { AttrNames } from './types.js' const configNameList = [ 'AutoConfiguration', 'ContainerConfiguration', ] interface GenKeyOptions extends Partial<TraceDecoratorOptions> { methodArgs: unknown[] decoratorContext: DecoratorContext callerClass: string callerMethod: string instance: DecoratorExecutorParam['instance'] } function genKey(options: GenKeyOptions): string { const { methodArgs, decoratorContext, spanName, spanNameDelimiter, instance, } = options const delimiter = spanNameDelimiter ? spanNameDelimiter : '/' switch (typeof spanName) { case 'string': { if (spanName.length > 0) { return spanName } break } case 'undefined': { let name = genEventKeyWhenSpanNameEmpty(options) if (! name) { name = `${options.callerClass.toString()}${delimiter}${options.callerMethod.toString()}` } return name } case 'function': { assert(instance, 'options.instance is required') const funcBind: KeyGenerator = isArrowFunction(spanName) ? spanName : spanName.bind(instance) const keyStr = funcBind(methodArgs as [], decoratorContext) assert( typeof keyStr === 'string' || typeof keyStr === 'undefined', 'keyGenerator function must return a string or undefined', ) if (keyStr) { return keyStr } break } /* c8 ignore next 3 */ default: { assert(false, 'spanName must be a string or a function') } } const name = `${options.callerClass.toString()}${delimiter}${options.callerMethod.toString()}` return name } /** * For TraceInit used on AutoConfiguration */ function genEventKeyWhenSpanNameEmpty(options: GenKeyOptions): string { const { callerClass, callerMethod, namespace, spanName, } = options assert(! spanName, 'spanName is not empty') let name = '' if (namespace && configNameList.includes(callerClass)) { switch (callerMethod) { /* c8 ignore next 4 */ case 'onConfigLoad': { name = `TraceInit ${namespace}.${options.callerMethod.toString()}` break } case 'onReady': { name = `TraceInit ${namespace}.${options.callerMethod.toString()}` break } case 'onServerReady': { name = `TraceInit ${namespace}.${options.callerMethod.toString()}` break } /* c8 ignore next 4 */ case 'onStop': { name = `TraceInit ${namespace}.${options.callerMethod.toString()}` break } /* c8 ignore next 4 */ case 'onHealthCheck': { name = `TraceInit ${namespace}.${options.callerMethod.toString()}` break } /* c8 ignore next 2 */ default: break } } return name } export function genDecoratorExecutorOptions( optionsBase: DecoratorExecutorParamBase<TraceDecoratorOptions>, optionsExt: GenDecoratorExecutorOptions, ): DecoratorExecutorParam<TraceDecoratorOptions> { const { methodArgs } = optionsBase const { traceService } = optionsExt assert(traceService, 'traceService is required') const { mergedDecoratorParam } = optionsBase assert(mergedDecoratorParam, 'mergedDecoratorParam is undefined') if (typeof mergedDecoratorParam.startActiveSpan !== 'boolean') { mergedDecoratorParam.startActiveSpan = true } if (typeof mergedDecoratorParam.autoEndSpan !== 'boolean') { mergedDecoratorParam.autoEndSpan = true } // DO NOT set traceContext // if (! mergedDecoratorParam.traceContext) { // mergedDecoratorParam.traceContext = traceService?.getActiveContext() // } const decoratorContext: DecoratorContext = { webApp: optionsBase.webApp, webContext: optionsBase.webContext, traceService, traceContext: mergedDecoratorParam.traceContext, traceSpan: void 0, traceScope: void 0, /** Caller Class name */ instanceName: optionsBase.instanceName, /** Caller method name */ methodName: optionsBase.methodName, instance: optionsBase.instance, } const keyOpts: GenKeyOptions = { ...mergedDecoratorParam, callerClass: optionsBase.instanceName, callerMethod: optionsBase.methodName, decoratorContext, methodArgs, instance: optionsBase.instance, } const spanName = genKey(keyOpts) assert(spanName, 'spanName is undefined') const callerAttr = { [AttrNames.CallerClass]: optionsBase.instanceName, [AttrNames.CallerMethod]: optionsBase.methodName, } let rootTraceContext: TraceContext if (optionsBase.webContext) { rootTraceContext = optionsBase.webContext.getAttr('rootTraceContext') } else { rootTraceContext = context.active() } if (optionsExt.config.enable) { assert(rootTraceContext, 'rootTraceContext is undefined') } const ret: DecoratorExecutorParam<TraceDecoratorOptions> = { ...optionsBase, ...optionsExt, rootTraceContext, callerAttr, spanName, spanOptions: mergedDecoratorParam, startActiveSpan: mergedDecoratorParam.startActiveSpan, traceContext: mergedDecoratorParam.traceContext, traceService, traceScope: void 0, span: void 0, } Object.defineProperty(ret, 'rootTraceContext', { writable: false, value: rootTraceContext, }) return ret } export function endTraceSpan(traceService: TraceServiceSpan, span: Span[], spanStatusOptions: SpanStatusOptions | undefined): void { const revertArr = span.slice().reverse() revertArr.forEach((span) => { _endTraceSpan(traceService, span, spanStatusOptions) }) } function _endTraceSpan(traceService: TraceServiceSpan, span: Span, spanStatusOptions: SpanStatusOptions | undefined): void { if (spanStatusOptions) { traceService.endSpan({ span, spanStatusOptions }) } else { traceService.endSpan({ span }) } }