From 2c4a83421c979f65fd464d9599882de9a65bbc74 Mon Sep 17 00:00:00 2001 From: Matteo Gazziola <15204191+matteoga@users.noreply.github.com> Date: Thu, 30 Dec 2021 10:24:55 +0100 Subject: [PATCH] fix(opentelemetry-instrumentation-nestjs-core): copy metadata to wrapped handler (#796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gerhard Stöbich Co-authored-by: Daniel Dyla --- .../src/instrumentation.ts | 10 ++++ .../test/index.test.ts | 34 ++++++++++++ .../test/setup.ts | 54 ++++++++++++++----- 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/plugins/node/opentelemetry-instrumentation-nestjs-core/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-nestjs-core/src/instrumentation.ts index 9aa1df479ab..b753901f014 100644 --- a/plugins/node/opentelemetry-instrumentation-nestjs-core/src/instrumentation.ts +++ b/plugins/node/opentelemetry-instrumentation-nestjs-core/src/instrumentation.ts @@ -239,6 +239,16 @@ function createWrapHandler( 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; } diff --git a/plugins/node/opentelemetry-instrumentation-nestjs-core/test/index.test.ts b/plugins/node/opentelemetry-instrumentation-nestjs-core/test/index.test.ts index 4dfbf2939a9..54059921288 100644 --- a/plugins/node/opentelemetry-instrumentation-nestjs-core/test/index.test.ts +++ b/plugins/node/opentelemetry-instrumentation-nestjs-core/test/index.test.ts @@ -110,6 +110,40 @@ describe('nestjs-core', () => { ]); }); + it('should not ovewrite metadata set on the request handler', async () => { + const path = semver.intersects(LIB_VERSION, '<5.0.0') ? '/' : '/metadata'; + const url = '/metadata'; + const instance = 'MetadataController'; + const callback = 'getMetadata'; + + assert.deepStrictEqual(await request('/metadata'), '["path","method"]'); + + assertSpans(memoryExporter.getFinishedSpans(), [ + { + type: 'app_creation', + service: 'test', + name: 'Create Nest App', + module: 'AppModule', + }, + { + type: 'handler', + service: 'test', + name: callback, + callback, + parentSpanName: `${instance}.${callback}`, + }, + { + type: 'request_context', + service: 'test', + name: `${instance}.${callback}`, + method: 'GET', + url, + path, + callback, + }, + ]); + }); + it('should capture errors', async () => { const path = semver.intersects(LIB_VERSION, '<5.0.0') ? '/' : '/errors'; const url = '/errors'; diff --git a/plugins/node/opentelemetry-instrumentation-nestjs-core/test/setup.ts b/plugins/node/opentelemetry-instrumentation-nestjs-core/test/setup.ts index c4ffc0e2809..1b77efe8ea1 100644 --- a/plugins/node/opentelemetry-instrumentation-nestjs-core/test/setup.ts +++ b/plugins/node/opentelemetry-instrumentation-nestjs-core/test/setup.ts @@ -139,6 +139,24 @@ export const setup = async (version: string): Promise => { YellInterceptor ); + let MetadataInterceptor = class MetadataInterceptor + implements NestInterceptor + { + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next + .handle() + .pipe(map(() => Reflect.getMetadataKeys(context.getHandler()))); + } + }; + MetadataInterceptor = __decorate( + [ + semver.intersects(version, '^4.0.0') + ? common.Interceptor() + : common.Injectable(), + ], + MetadataInterceptor + ); + const [UsersController, UsersModule] = makeModule( 'Users', () => 'Hello, world!\n', @@ -147,20 +165,12 @@ export const setup = async (version: string): Promise => { const [GuardedController, GuardedModule] = makeModule( 'Guarded', () => 'Hello, guarded!\n', - [ - common.Controller('guarded'), - common.UseGuards(MyGuard), - common.UseGuards(MyGuard), - ] + [common.Controller('guarded'), common.UseGuards(MyGuard)] ); const [InterceptedController, InterceptedModule] = makeModule( 'Intercepted', () => 'Hello, Intercepted!\n', - [ - common.Controller('intercepted'), - common.UseInterceptors(YellInterceptor), - common.UseInterceptors(YellInterceptor), - ] + [common.Controller('intercepted'), common.UseInterceptors(YellInterceptor)] ); const [ErrorController, ErrorModule] = makeModule( 'Error', @@ -170,16 +180,29 @@ export const setup = async (version: string): Promise => { [common.Controller('errors')] ); + const [MetadataController, MetadataModule] = makeModule( + 'Metadata', + () => 'Hello, Metadata!\n', + [common.Controller('metadata'), common.UseInterceptors(MetadataInterceptor)] + ); + if (semver.intersects(version, '>=4.6.3')) { AppModule = __decorate( [ common.Module({ - imports: [UsersModule, ErrorModule, GuardedModule, InterceptedModule], + imports: [ + UsersModule, + ErrorModule, + GuardedModule, + InterceptedModule, + MetadataModule, + ], controllers: [ UsersController, ErrorController, GuardedController, InterceptedController, + MetadataController, ], }), ], @@ -189,12 +212,19 @@ export const setup = async (version: string): Promise => { AppModule = __decorate( [ common.Module({ - modules: [UsersModule, ErrorModule, GuardedModule, InterceptedModule], + modules: [ + UsersModule, + ErrorModule, + GuardedModule, + InterceptedModule, + MetadataModule, + ], controllers: [ UsersController, ErrorController, GuardedController, InterceptedController, + MetadataController, ], }), ],