From 553d7b291a567dd6b6b6d43c97f52d5af0391bb5 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 3 Oct 2022 10:37:21 -0700 Subject: [PATCH] fix: captureMethod correctly detect method name when used with external decorators --- packages/tracer/src/Tracer.ts | 8 ++- packages/tracer/tests/unit/Tracer.test.ts | 67 ++++++++++++++++++++++- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index e7663b2673..37a7dd43a7 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -420,7 +420,7 @@ class Tracer extends Utility implements TracerInterface { * @decorator Class */ public captureMethod(options?: HandlerOptions): MethodDecorator { - return (_target, _propertyKey, descriptor) => { + return (_target, propertyKey, descriptor) => { // The descriptor.value is the method this decorator decorates, it cannot be undefined. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const originalMethod = descriptor.value!; @@ -434,12 +434,14 @@ class Tracer extends Utility implements TracerInterface { return originalMethod.apply(this, [...args]); } - return tracerRef.provider.captureAsyncFunc(`### ${originalMethod.name}`, async subsegment => { + const methodName = String(propertyKey); + + return tracerRef.provider.captureAsyncFunc(`### ${methodName}`, async subsegment => { let result; try { result = await originalMethod.apply(this, [...args]); if (options?.captureResponse ?? true) { - tracerRef.addResponseAsMetadata(result, originalMethod.name); + tracerRef.addResponseAsMetadata(result, methodName); } } catch (error) { tracerRef.addErrorAsMetadata(error as Error); diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 4e77ebf490..fd8c8e0621 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -15,10 +15,12 @@ interface LambdaInterface { } type CaptureAsyncFuncMock = jest.SpyInstance unknown, parent?: Segment | Subsegment]>; -const createCaptureAsyncFuncMock = function(provider: ProviderServiceInterface): CaptureAsyncFuncMock { +const createCaptureAsyncFuncMock = function(provider: ProviderServiceInterface, subsegment?: Subsegment): CaptureAsyncFuncMock { return jest.spyOn(provider, 'captureAsyncFunc') .mockImplementation(async (methodName, callBackFn) => { - const subsegment = new Subsegment(`### ${methodName}`); + if (!subsegment) { + subsegment = new Subsegment(`### ${methodName}`); + } jest.spyOn(subsegment, 'flush').mockImplementation(() => null); await callBackFn(subsegment); }); @@ -1239,6 +1241,67 @@ describe('Class: Tracer', () => { }); + test('when used as decorator together with another external decorator, the method name is detected properly', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + createCaptureAsyncFuncMock(tracer.provider, newSubsegment); + + // Creating custom external decorator + // eslint-disable-next-line func-style + function passThrough() { + // A decorator that calls the original method. + return ( + _target: unknown, + _propertyKey: string, + descriptor: PropertyDescriptor + ) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const originalMethod = descriptor.value!; + descriptor.value = function (...args: unknown[]) { + return originalMethod.apply(this, [...args]); + }; + }; + } + + class Lambda implements LambdaInterface { + @tracer.captureMethod() + @passThrough() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async dummyMethod(): Promise { + return `foo`; + } + + public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { + await this.dummyMethod(); + + return; + } + + } + + // Act / Assess + const lambda = new Lambda(); + const handler = lambda.handler.bind(lambda); + await handler({}, context, () => console.log('Lambda invoked!')); + + // Assess + expect(newSubsegment).toEqual(expect.objectContaining({ + metadata: { + 'hello-world': { + // Assess that the method name is added correctly + 'dummyMethod response': 'foo', + }, + } + })); + + }); + }); describe('Method: captureAWS', () => {