import { NestFactoryStatic } from '@nestjs/core/nest-factory.js' import { AttributeNames, NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core' import { api } from '@opentelemetry/sdk-node' import { NestFactory } from '@nestjs/core' import { NestType } from '@opentelemetry/instrumentation-nestjs-core/build/src/enums/index.js' import { RouterExecutionContext } from '@nestjs/core/router/router-execution-context.js' import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_ROUTE, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions' import { isWrapped } from '@opentelemetry/instrumentation' import { Tracer } from '@opentelemetry/api' import { Controller } from '@nestjs/common/interfaces' const addError = (span: api.Span, error: Error) => { span.recordException(error); span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message }); return error; }; function createWrapNestFactoryCreate( tracer: api.Tracer, moduleVersion?: string ) { return function wrapCreate(original: typeof NestFactory.create) { return function createWithTrace( this: typeof NestFactory, nestModule: any /* serverOrOptions */ ) { const span = tracer.startSpan('Create Nest App', { attributes: { ...NestInstrumentation.COMMON_ATTRIBUTES, [AttributeNames.TYPE]: NestType.APP_CREATION, [AttributeNames.VERSION]: moduleVersion, [AttributeNames.MODULE]: nestModule.name, }, }); const spanContext = api.trace.setSpan(api.context.active(), span); return api.context.with(spanContext, async () => { try { return await original.apply(this, arguments as any); } catch (e: any) { throw addError(span, e); } finally { span.end(); } }); }; }; } function createWrapHandler( tracer: api.Tracer, moduleVersion: string | undefined, handler: Function ) { const spanName = handler.name || 'anonymous nest handler'; const options = { attributes: { ...NestInstrumentation.COMMON_ATTRIBUTES, [AttributeNames.VERSION]: moduleVersion, [AttributeNames.TYPE]: NestType.REQUEST_HANDLER, [AttributeNames.CALLBACK]: handler.name, }, }; const wrappedHandler = function (this: RouterExecutionContext) { const span = tracer.startSpan(spanName, options); const spanContext = api.trace.setSpan(api.context.active(), span); return api.context.with(spanContext, async () => { try { return await handler.apply(this, arguments); } catch (e: any) { throw addError(span, e); } finally { span.end(); } }); }; if (handler.name) { Object.defineProperty(wrappedHandler, 'name', { value: handler.name }); } // Get the current metadata and set onto the wrapper to ensure other decorators ( ie: NestJS EventPattern / RolesGuard ) // won't be affected by the use of this instrumentation Reflect.getMetadataKeys(handler).forEach(metadataKey => { Reflect.defineMetadata( metadataKey, Reflect.getMetadata(metadataKey, handler), wrappedHandler ); }); return wrappedHandler; } function createWrapCreateHandler(tracer: api.Tracer, moduleVersion?: string) { return function wrapCreateHandler( original: RouterExecutionContext['create'] ) { return function createHandlerWithTrace( this: RouterExecutionContext, instance: Controller, callback: (...args: any[]) => unknown ) { arguments[1] = createWrapHandler(tracer, moduleVersion, callback); const handler = original.apply(this, arguments as any); const callbackName = callback.name; const instanceName = instance.constructor && instance.constructor.name ? instance.constructor.name : 'UnnamedInstance'; const spanName = callbackName ? `${instanceName}.${callbackName}` : instanceName; return function ( this: any, req: any, res: any, next: (...args: any[]) => unknown ) { const span = tracer.startSpan(spanName, { attributes: { ...NestInstrumentation.COMMON_ATTRIBUTES, [AttributeNames.VERSION]: moduleVersion, [AttributeNames.TYPE]: NestType.REQUEST_CONTEXT, [SEMATTRS_HTTP_METHOD]: req.method, [SEMATTRS_HTTP_URL]: req.originalUrl || req.url, [SEMATTRS_HTTP_ROUTE]: req.route?.path || req.routeOptions?.url || req.routerPath, [AttributeNames.CONTROLLER]: instanceName, [AttributeNames.CALLBACK]: callbackName, }, }); const spanContext = api.trace.setSpan(api.context.active(), span); return api.context.with(spanContext, async () => { try { return await handler.apply(this, arguments as any); } catch (e: any) { throw addError(span, e); } finally { span.end(); } }); }; }; }; } export function initNest(tracer: Tracer) { const ins = new NestInstrumentation() //@ts-ignore ins.ensureWrapped( NestFactoryStatic.prototype, 'create', // @ts-ignore createWrapNestFactoryCreate(tracer, '10.0') ); //@ts-ignore ins.ensureWrapped( RouterExecutionContext.prototype, 'create', createWrapCreateHandler(tracer, '10.0') ); }