From cc59160cad99f46a5389c55c06edc6939a6225f0 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Thu, 18 Aug 2022 18:27:12 +0000 Subject: [PATCH 01/10] feat(tracer): use decorator options to disable exception and result capture --- docs/core/tracer.md | 68 ++++++++++ packages/tracer/src/Tracer.ts | 28 +++- packages/tracer/src/types/Tracer.ts | 40 ++++++ packages/tracer/tests/unit/Tracer.test.ts | 152 ++++++++++++++++++++++ 4 files changed, 281 insertions(+), 7 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index fc4b927d82..2e92e08e30 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -412,6 +412,40 @@ Use **`POWERTOOLS_TRACER_CAPTURE_RESPONSE=false`** environment variable to instr 2. You might manipulate **streaming objects that can be read only once**; this prevents subsequent calls from being empty 3. You might return **more than 64K** of data _e.g., `message too long` error_ +### Disabling response capture for targeted methods and handlers + +Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize function responses as metadata. + +=== "method.ts" + + ```typescript hl_lines="5" + import { Tracer } from '@aws-lambda-powertools/tracer'; + + const tracer = new Tracer({ serviceName: 'serverlessAirline' }); + class MyThing { + @tracer.captureMethod({ captureResponse: false }) + myMethod(): string { + /* ... */ + return 'foo bar'; + } + } + ``` + +=== "handler.ts" + + ```typescript hl_lines="6" + import { Tracer } from '@aws-lambda-powertools/tracer'; + import { LambdaInterface } from '@aws-lambda-powertools/commons'; + + const tracer = new Tracer({ serviceName: 'serverlessAirline' }); + class MyHandler implements LambdaInterface { + @tracer.captureLambdaHandler({ captureResponse: false }) + async handler(_event: any, _context: any): Promise { + /* ... */ + } + } + ``` + ### Disabling exception auto-capture Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct Tracer **not** to serialize exceptions as metadata. @@ -420,6 +454,40 @@ Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct 1. You might **return sensitive** information from exceptions, stack traces you might not control +### Disabling exception capture for targeted methods and handlers + +Use the `captureError: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize exceptions as metadata. + +=== "method.ts" + + ```typescript hl_lines="5" + import { Tracer } from '@aws-lambda-powertools/tracer'; + + const tracer = new Tracer({ serviceName: 'serverlessAirline' }); + class MyThing { + @tracer.captureMethod({ captureError: false }) + myMethod(): string { + /* ... */ + return 'foo bar'; + } + } + ``` + +=== "handler.ts" + + ```typescript hl_lines="6" + import { Tracer } from '@aws-lambda-powertools/tracer'; + import { LambdaInterface } from '@aws-lambda-powertools/commons'; + + const tracer = new Tracer({ serviceName: 'serverlessAirline' }); + class MyHandler implements LambdaInterface { + @tracer.captureLambdaHandler({ captureError: false }) + async handler(_event: any, _context: any): Promise { + /* ... */ + } + } + ``` + ### Escape hatch mechanism You can use `tracer.provider` attribute to access all methods provided by the [AWS X-Ray SDK](https://docs.aws.amazon.com/xray-sdk-for-nodejs/latest/reference/AWSXRay.html). diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 9a572f45a3..f50b4992c8 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -2,7 +2,7 @@ import { Handler } from 'aws-lambda'; import { AsyncHandler, SyncHandler, Utility } from '@aws-lambda-powertools/commons'; import { TracerInterface } from '.'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; -import { HandlerMethodDecorator, TracerOptions, MethodDecorator } from './types'; +import { HandlerMethodDecorator, TracerOptions, TracerCaptureMethodOptions, TracerCaptureLambdaHandlerOptions, MethodDecorator } from './types'; import { ProviderService, ProviderServiceInterface } from './provider'; import { Segment, Subsegment } from 'aws-xray-sdk-core'; @@ -339,7 +339,7 @@ class Tracer extends Utility implements TracerInterface { * * @decorator Class */ - public captureLambdaHandler(): HandlerMethodDecorator { + public captureLambdaHandler(options?: TracerCaptureLambdaHandlerOptions): HandlerMethodDecorator { return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. @@ -365,9 +365,16 @@ class Tracer extends Utility implements TracerInterface { let result: unknown; try { result = await originalMethod.apply(handlerRef, [ event, context, callback ]); - tracerRef.addResponseAsMetadata(result, process.env._HANDLER); + if (options?.captureResponse ?? true) { + tracerRef.addResponseAsMetadata(result, process.env._HANDLER); + } + } catch (error) { - tracerRef.addErrorAsMetadata(error as Error); + if (options?.captureError ?? true) { + tracerRef.addErrorAsMetadata(error as Error); + } else { + tracerRef.getSegment().addErrorFlag(); + } throw error; } finally { subsegment?.close(); @@ -416,7 +423,7 @@ class Tracer extends Utility implements TracerInterface { * * @decorator Class */ - public captureMethod(): MethodDecorator { + public captureMethod(options?: TracerCaptureMethodOptions): MethodDecorator { 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 @@ -435,9 +442,16 @@ class Tracer extends Utility implements TracerInterface { let result; try { result = await originalMethod.apply(this, [...args]); - tracerRef.addResponseAsMetadata(result, originalMethod.name); + if (options?.captureResponse ?? true) { + tracerRef.addResponseAsMetadata(result, originalMethod.name); + } + } catch (error) { - tracerRef.addErrorAsMetadata(error as Error); + if (options?.captureError ?? true) { + tracerRef.addErrorAsMetadata(error as Error); + } else { + tracerRef.getSegment().addErrorFlag(); + } throw error; } finally { diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index 288a65c23e..4e81870858 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -26,6 +26,44 @@ type TracerOptions = { customConfigService?: ConfigServiceInterface }; +/** + * Options for the captureMethod decorator to be used when decorating a method. + * + * Usage: + * @example + * ```typescript + * const tracer = new Tracer(); + * + * class MyThing { + * @tracer.captureMethod({ captureResponse: false, captureError: false }) + * async myMethod() { ... } + * } + * ``` + */ +type TracerCaptureMethodOptions = { + captureResponse?: boolean + captureError?: boolean +}; + +/** + * Options for the captureLambdaHandler decorator to be used when decorating a method. + * + * Usage: + * @example + * ```typescript + * const tracer = new Tracer(); + * + * class MyThing implements LambdaInterface { + * @tracer.captureLambdaHandler({ captureResponse: false, captureError: false }) + * async handler() { ... } + * } + * ``` + */ +type TracerCaptureLambdaHandlerOptions = { + captureResponse?: boolean + captureError?: boolean +}; + type HandlerMethodDecorator = ( target: LambdaInterface, propertyKey: string | symbol, @@ -38,6 +76,8 @@ type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: T export { TracerOptions, + TracerCaptureLambdaHandlerOptions, + TracerCaptureMethodOptions, HandlerMethodDecorator, MethodDecorator }; \ No newline at end of file diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index b0100b42d3..55cbb47112 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -648,6 +648,36 @@ describe('Class: Tracer', () => { }); + test('when used as decorator while captureResponse is set to false, it does not capture the response as metadata', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); + jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + class Lambda implements LambdaInterface { + + @tracer.captureLambdaHandler({ captureResponse: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + return new Promise((resolve, _reject) => resolve({ + foo: 'bar' + } as unknown as TResult)); + } + + } + + // Act + await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect('metadata' in newSubsegment).toBe(false); + + }); + test('when used as decorator and with standard config, it captures the response as metadata', async () => { // Prepare @@ -727,6 +757,41 @@ describe('Class: Tracer', () => { }); + test('when used as decorator while captureError is set to false, it does not capture the exceptions', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); + const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); + class Lambda implements LambdaInterface { + + @tracer.captureLambdaHandler({ captureError: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + throw new Error('Exception thrown!'); + } + + } + + // Act & Assess + await expect(new Lambda().handler({}, context, () => console.log('Lambda invoked!'))).rejects.toThrowError(Error); + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect(newSubsegment).toEqual(expect.objectContaining({ + name: '## index.handler', + })); + expect('cause' in newSubsegment).toBe(false); + expect(addErrorFlagSpy).toHaveBeenCalledTimes(1); + expect(addErrorSpy).toHaveBeenCalledTimes(0); + expect.assertions(6); + + }); + test('when used as decorator and with standard config, it captures the exception correctly', async () => { // Prepare @@ -964,6 +1029,52 @@ describe('Class: Tracer', () => { }); + test('when used as decorator and with captureResponse set to false, it does not capture the response as metadata', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + jest.spyOn(newSubsegment, 'flush').mockImplementation(() => null); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + class Lambda implements LambdaInterface { + + @tracer.captureMethod({ captureResponse: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async dummyMethod(some: string): Promise { + return new Promise((resolve, _reject) => setTimeout(() => resolve(some), 3000)); + } + + public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { + const result = await this.dummyMethod('foo bar'); + + return new Promise((resolve, _reject) => resolve(result as unknown as TResult)); + } + + } + + // Act + await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect(captureAsyncFuncSpy).toHaveBeenCalledWith('### dummyMethod', expect.anything()); + expect(newSubsegment).toEqual(expect.objectContaining({ + name: '### dummyMethod', + })); + expect(newSubsegment).not.toEqual(expect.objectContaining({ + metadata: { + 'hello-world': { + 'dummyMethod response': 'foo bar', + }, + } + })); + + }); + test('when used as decorator and with standard config, it captures the exception correctly', async () => { // Prepare @@ -1004,6 +1115,47 @@ describe('Class: Tracer', () => { }); + test('when used as decorator and with captureError set to false, it does not capture the exception', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = createCaptureAsyncFuncMock(tracer.provider); + const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); + const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); + class Lambda implements LambdaInterface { + + @tracer.captureMethod({ captureError: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async dummyMethod(_some: string): Promise { + throw new Error('Exception thrown!'); + } + + public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { + const result = await this.dummyMethod('foo bar'); + + return new Promise((resolve, _reject) => resolve(result as unknown as TResult)); + } + + } + + // Act / Assess + await expect(new Lambda().handler({}, context, () => console.log('Lambda invoked!'))).rejects.toThrowError(Error); + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect(newSubsegment).toEqual(expect.objectContaining({ + name: '### dummyMethod', + })); + expect('cause' in newSubsegment).toBe(false); + expect(addErrorFlagSpy).toHaveBeenCalledTimes(1); + expect(addErrorSpy).toHaveBeenCalledTimes(0); + expect.assertions(6); + + }); + test('when used as decorator and when calling other methods/props in the class they are called in the orginal scope', async () => { // Prepare From 6eedbbea82d7d506d1b13f768ec1605d09778226 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Thu, 18 Aug 2022 18:42:12 +0000 Subject: [PATCH 02/10] docs: small inline docs improvements --- packages/tracer/src/Tracer.ts | 4 ++++ packages/tracer/src/types/Tracer.ts | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index f50b4992c8..e2927d2c31 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -373,6 +373,8 @@ class Tracer extends Utility implements TracerInterface { if (options?.captureError ?? true) { tracerRef.addErrorAsMetadata(error as Error); } else { + // Though we aren't adding the error as metadata, we should still + // mark the segment as having an error. tracerRef.getSegment().addErrorFlag(); } throw error; @@ -450,6 +452,8 @@ class Tracer extends Utility implements TracerInterface { if (options?.captureError ?? true) { tracerRef.addErrorAsMetadata(error as Error); } else { + // Though we aren't adding the error as metadata, we should still + // mark the segment as having an error. tracerRef.getSegment().addErrorFlag(); } diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index 4e81870858..df310b31a7 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -36,7 +36,9 @@ type TracerOptions = { * * class MyThing { * @tracer.captureMethod({ captureResponse: false, captureError: false }) - * async myMethod() { ... } + * myMethod(): string { + * return 'foo bar'; + * } * } * ``` */ @@ -55,7 +57,7 @@ type TracerCaptureMethodOptions = { * * class MyThing implements LambdaInterface { * @tracer.captureLambdaHandler({ captureResponse: false, captureError: false }) - * async handler() { ... } + * async handler(_event: any, _context: any): Promise {} * } * ``` */ From 14ccc2f36d1f0fce11528ae43801bfcc0e1505f7 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Thu, 18 Aug 2022 18:54:17 +0000 Subject: [PATCH 03/10] chore: remove the captureError option --- docs/core/tracer.md | 34 ---------- packages/tracer/src/Tracer.ts | 17 +---- packages/tracer/src/types/Tracer.ts | 6 +- packages/tracer/tests/unit/Tracer.test.ts | 76 ----------------------- 4 files changed, 4 insertions(+), 129 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index 2e92e08e30..b2e40336fb 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -454,40 +454,6 @@ Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct 1. You might **return sensitive** information from exceptions, stack traces you might not control -### Disabling exception capture for targeted methods and handlers - -Use the `captureError: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize exceptions as metadata. - -=== "method.ts" - - ```typescript hl_lines="5" - import { Tracer } from '@aws-lambda-powertools/tracer'; - - const tracer = new Tracer({ serviceName: 'serverlessAirline' }); - class MyThing { - @tracer.captureMethod({ captureError: false }) - myMethod(): string { - /* ... */ - return 'foo bar'; - } - } - ``` - -=== "handler.ts" - - ```typescript hl_lines="6" - import { Tracer } from '@aws-lambda-powertools/tracer'; - import { LambdaInterface } from '@aws-lambda-powertools/commons'; - - const tracer = new Tracer({ serviceName: 'serverlessAirline' }); - class MyHandler implements LambdaInterface { - @tracer.captureLambdaHandler({ captureError: false }) - async handler(_event: any, _context: any): Promise { - /* ... */ - } - } - ``` - ### Escape hatch mechanism You can use `tracer.provider` attribute to access all methods provided by the [AWS X-Ray SDK](https://docs.aws.amazon.com/xray-sdk-for-nodejs/latest/reference/AWSXRay.html). diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index e2927d2c31..0fc163eccd 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -370,13 +370,7 @@ class Tracer extends Utility implements TracerInterface { } } catch (error) { - if (options?.captureError ?? true) { - tracerRef.addErrorAsMetadata(error as Error); - } else { - // Though we aren't adding the error as metadata, we should still - // mark the segment as having an error. - tracerRef.getSegment().addErrorFlag(); - } + tracerRef.addErrorAsMetadata(error as Error); throw error; } finally { subsegment?.close(); @@ -449,14 +443,7 @@ class Tracer extends Utility implements TracerInterface { } } catch (error) { - if (options?.captureError ?? true) { - tracerRef.addErrorAsMetadata(error as Error); - } else { - // Though we aren't adding the error as metadata, we should still - // mark the segment as having an error. - tracerRef.getSegment().addErrorFlag(); - } - + tracerRef.addErrorAsMetadata(error as Error); throw error; } finally { subsegment?.close(); diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index df310b31a7..ce0ba2e31e 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -35,7 +35,7 @@ type TracerOptions = { * const tracer = new Tracer(); * * class MyThing { - * @tracer.captureMethod({ captureResponse: false, captureError: false }) + * @tracer.captureMethod({ captureResponse: false }) * myMethod(): string { * return 'foo bar'; * } @@ -44,7 +44,6 @@ type TracerOptions = { */ type TracerCaptureMethodOptions = { captureResponse?: boolean - captureError?: boolean }; /** @@ -56,14 +55,13 @@ type TracerCaptureMethodOptions = { * const tracer = new Tracer(); * * class MyThing implements LambdaInterface { - * @tracer.captureLambdaHandler({ captureResponse: false, captureError: false }) + * @tracer.captureLambdaHandler({ captureResponse: false }) * async handler(_event: any, _context: any): Promise {} * } * ``` */ type TracerCaptureLambdaHandlerOptions = { captureResponse?: boolean - captureError?: boolean }; type HandlerMethodDecorator = ( diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 55cbb47112..03cae07d8d 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -757,41 +757,6 @@ describe('Class: Tracer', () => { }); - test('when used as decorator while captureError is set to false, it does not capture the exceptions', async () => { - - // Prepare - const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); - const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); - const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); - class Lambda implements LambdaInterface { - - @tracer.captureLambdaHandler({ captureError: false }) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - throw new Error('Exception thrown!'); - } - - } - - // Act & Assess - await expect(new Lambda().handler({}, context, () => console.log('Lambda invoked!'))).rejects.toThrowError(Error); - expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - })); - expect('cause' in newSubsegment).toBe(false); - expect(addErrorFlagSpy).toHaveBeenCalledTimes(1); - expect(addErrorSpy).toHaveBeenCalledTimes(0); - expect.assertions(6); - - }); - test('when used as decorator and with standard config, it captures the exception correctly', async () => { // Prepare @@ -1115,47 +1080,6 @@ describe('Class: Tracer', () => { }); - test('when used as decorator and with captureError set to false, it does not capture the exception', async () => { - - // Prepare - const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const captureAsyncFuncSpy = createCaptureAsyncFuncMock(tracer.provider); - const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); - const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); - class Lambda implements LambdaInterface { - - @tracer.captureMethod({ captureError: false }) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - public async dummyMethod(_some: string): Promise { - throw new Error('Exception thrown!'); - } - - public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { - const result = await this.dummyMethod('foo bar'); - - return new Promise((resolve, _reject) => resolve(result as unknown as TResult)); - } - - } - - // Act / Assess - await expect(new Lambda().handler({}, context, () => console.log('Lambda invoked!'))).rejects.toThrowError(Error); - expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '### dummyMethod', - })); - expect('cause' in newSubsegment).toBe(false); - expect(addErrorFlagSpy).toHaveBeenCalledTimes(1); - expect(addErrorSpy).toHaveBeenCalledTimes(0); - expect.assertions(6); - - }); - test('when used as decorator and when calling other methods/props in the class they are called in the orginal scope', async () => { // Prepare From ae72d52fae318a21b308ad89598134eeb105b3f7 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Thu, 18 Aug 2022 19:03:40 +0000 Subject: [PATCH 04/10] chore: remove all captureLambdaHandler decorator changes --- docs/core/tracer.md | 17 +------------ packages/tracer/src/Tracer.ts | 10 +++----- packages/tracer/src/types/Tracer.ts | 19 -------------- packages/tracer/tests/unit/Tracer.test.ts | 30 ----------------------- 4 files changed, 5 insertions(+), 71 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index b2e40336fb..db691bc553 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -414,7 +414,7 @@ Use **`POWERTOOLS_TRACER_CAPTURE_RESPONSE=false`** environment variable to instr ### Disabling response capture for targeted methods and handlers -Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize function responses as metadata. +Use the `captureResponse: false` option in `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize function responses as metadata. === "method.ts" @@ -431,21 +431,6 @@ Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` } ``` -=== "handler.ts" - - ```typescript hl_lines="6" - import { Tracer } from '@aws-lambda-powertools/tracer'; - import { LambdaInterface } from '@aws-lambda-powertools/commons'; - - const tracer = new Tracer({ serviceName: 'serverlessAirline' }); - class MyHandler implements LambdaInterface { - @tracer.captureLambdaHandler({ captureResponse: false }) - async handler(_event: any, _context: any): Promise { - /* ... */ - } - } - ``` - ### Disabling exception auto-capture Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct Tracer **not** to serialize exceptions as metadata. diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 0fc163eccd..f73f2cb22d 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -2,7 +2,7 @@ import { Handler } from 'aws-lambda'; import { AsyncHandler, SyncHandler, Utility } from '@aws-lambda-powertools/commons'; import { TracerInterface } from '.'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; -import { HandlerMethodDecorator, TracerOptions, TracerCaptureMethodOptions, TracerCaptureLambdaHandlerOptions, MethodDecorator } from './types'; +import { HandlerMethodDecorator, TracerOptions, TracerCaptureMethodOptions, MethodDecorator } from './types'; import { ProviderService, ProviderServiceInterface } from './provider'; import { Segment, Subsegment } from 'aws-xray-sdk-core'; @@ -339,7 +339,7 @@ class Tracer extends Utility implements TracerInterface { * * @decorator Class */ - public captureLambdaHandler(options?: TracerCaptureLambdaHandlerOptions): HandlerMethodDecorator { + public captureLambdaHandler(): HandlerMethodDecorator { return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. @@ -365,10 +365,7 @@ class Tracer extends Utility implements TracerInterface { let result: unknown; try { result = await originalMethod.apply(handlerRef, [ event, context, callback ]); - if (options?.captureResponse ?? true) { - tracerRef.addResponseAsMetadata(result, process.env._HANDLER); - } - + tracerRef.addResponseAsMetadata(result, process.env._HANDLER); } catch (error) { tracerRef.addErrorAsMetadata(error as Error); throw error; @@ -444,6 +441,7 @@ class Tracer extends Utility implements TracerInterface { } catch (error) { tracerRef.addErrorAsMetadata(error as Error); + throw error; } finally { subsegment?.close(); diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index ce0ba2e31e..22a94fcc78 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -46,24 +46,6 @@ type TracerCaptureMethodOptions = { captureResponse?: boolean }; -/** - * Options for the captureLambdaHandler decorator to be used when decorating a method. - * - * Usage: - * @example - * ```typescript - * const tracer = new Tracer(); - * - * class MyThing implements LambdaInterface { - * @tracer.captureLambdaHandler({ captureResponse: false }) - * async handler(_event: any, _context: any): Promise {} - * } - * ``` - */ -type TracerCaptureLambdaHandlerOptions = { - captureResponse?: boolean -}; - type HandlerMethodDecorator = ( target: LambdaInterface, propertyKey: string | symbol, @@ -76,7 +58,6 @@ type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: T export { TracerOptions, - TracerCaptureLambdaHandlerOptions, TracerCaptureMethodOptions, HandlerMethodDecorator, MethodDecorator diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 03cae07d8d..27c5270b39 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -648,36 +648,6 @@ describe('Class: Tracer', () => { }); - test('when used as decorator while captureResponse is set to false, it does not capture the response as metadata', async () => { - - // Prepare - const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); - class Lambda implements LambdaInterface { - - @tracer.captureLambdaHandler({ captureResponse: false }) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { - return new Promise((resolve, _reject) => resolve({ - foo: 'bar' - } as unknown as TResult)); - } - - } - - // Act - await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); - - // Assess - expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect('metadata' in newSubsegment).toBe(false); - - }); - test('when used as decorator and with standard config, it captures the response as metadata', async () => { // Prepare From 81fd6980370d6ef752bc4c63f4529dce339b0c71 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Thu, 18 Aug 2022 17:50:22 -0600 Subject: [PATCH 05/10] Revert "chore: remove all captureLambdaHandler decorator changes" This reverts commit ae72d52fae318a21b308ad89598134eeb105b3f7. --- docs/core/tracer.md | 17 ++++++++++++- packages/tracer/src/Tracer.ts | 10 +++++--- packages/tracer/src/types/Tracer.ts | 19 ++++++++++++++ packages/tracer/tests/unit/Tracer.test.ts | 30 +++++++++++++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index db691bc553..b2e40336fb 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -414,7 +414,7 @@ Use **`POWERTOOLS_TRACER_CAPTURE_RESPONSE=false`** environment variable to instr ### Disabling response capture for targeted methods and handlers -Use the `captureResponse: false` option in `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize function responses as metadata. +Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize function responses as metadata. === "method.ts" @@ -431,6 +431,21 @@ Use the `captureResponse: false` option in `tracer.captureMethod()` decorators t } ``` +=== "handler.ts" + + ```typescript hl_lines="6" + import { Tracer } from '@aws-lambda-powertools/tracer'; + import { LambdaInterface } from '@aws-lambda-powertools/commons'; + + const tracer = new Tracer({ serviceName: 'serverlessAirline' }); + class MyHandler implements LambdaInterface { + @tracer.captureLambdaHandler({ captureResponse: false }) + async handler(_event: any, _context: any): Promise { + /* ... */ + } + } + ``` + ### Disabling exception auto-capture Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct Tracer **not** to serialize exceptions as metadata. diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index f73f2cb22d..0fc163eccd 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -2,7 +2,7 @@ import { Handler } from 'aws-lambda'; import { AsyncHandler, SyncHandler, Utility } from '@aws-lambda-powertools/commons'; import { TracerInterface } from '.'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; -import { HandlerMethodDecorator, TracerOptions, TracerCaptureMethodOptions, MethodDecorator } from './types'; +import { HandlerMethodDecorator, TracerOptions, TracerCaptureMethodOptions, TracerCaptureLambdaHandlerOptions, MethodDecorator } from './types'; import { ProviderService, ProviderServiceInterface } from './provider'; import { Segment, Subsegment } from 'aws-xray-sdk-core'; @@ -339,7 +339,7 @@ class Tracer extends Utility implements TracerInterface { * * @decorator Class */ - public captureLambdaHandler(): HandlerMethodDecorator { + public captureLambdaHandler(options?: TracerCaptureLambdaHandlerOptions): HandlerMethodDecorator { return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. @@ -365,7 +365,10 @@ class Tracer extends Utility implements TracerInterface { let result: unknown; try { result = await originalMethod.apply(handlerRef, [ event, context, callback ]); - tracerRef.addResponseAsMetadata(result, process.env._HANDLER); + if (options?.captureResponse ?? true) { + tracerRef.addResponseAsMetadata(result, process.env._HANDLER); + } + } catch (error) { tracerRef.addErrorAsMetadata(error as Error); throw error; @@ -441,7 +444,6 @@ class Tracer extends Utility implements TracerInterface { } catch (error) { tracerRef.addErrorAsMetadata(error as Error); - throw error; } finally { subsegment?.close(); diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index 22a94fcc78..ce0ba2e31e 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -46,6 +46,24 @@ type TracerCaptureMethodOptions = { captureResponse?: boolean }; +/** + * Options for the captureLambdaHandler decorator to be used when decorating a method. + * + * Usage: + * @example + * ```typescript + * const tracer = new Tracer(); + * + * class MyThing implements LambdaInterface { + * @tracer.captureLambdaHandler({ captureResponse: false }) + * async handler(_event: any, _context: any): Promise {} + * } + * ``` + */ +type TracerCaptureLambdaHandlerOptions = { + captureResponse?: boolean +}; + type HandlerMethodDecorator = ( target: LambdaInterface, propertyKey: string | symbol, @@ -58,6 +76,7 @@ type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: T export { TracerOptions, + TracerCaptureLambdaHandlerOptions, TracerCaptureMethodOptions, HandlerMethodDecorator, MethodDecorator diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 27c5270b39..03cae07d8d 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -648,6 +648,36 @@ describe('Class: Tracer', () => { }); + test('when used as decorator while captureResponse is set to false, it does not capture the response as metadata', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); + jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + class Lambda implements LambdaInterface { + + @tracer.captureLambdaHandler({ captureResponse: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + return new Promise((resolve, _reject) => resolve({ + foo: 'bar' + } as unknown as TResult)); + } + + } + + // Act + await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect('metadata' in newSubsegment).toBe(false); + + }); + test('when used as decorator and with standard config, it captures the response as metadata', async () => { // Prepare From e4566701accaa12eb0299ee2c3d845065dde082f Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Thu, 18 Aug 2022 18:02:05 -0600 Subject: [PATCH 06/10] feat: add middy middleware options --- docs/core/tracer.md | 21 ++++++++++++++++++++- packages/tracer/src/middleware/middy.ts | 12 +++++++++--- packages/tracer/tests/unit/middy.test.ts | 22 ++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index b2e40336fb..f938f617a4 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -414,7 +414,7 @@ Use **`POWERTOOLS_TRACER_CAPTURE_RESPONSE=false`** environment variable to instr ### Disabling response capture for targeted methods and handlers -Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators to instruct Tracer **not** to serialize function responses as metadata. +Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators, or use the same option in the middy `captureLambdaHander` middleware to instruct Tracer **not** to serialize function responses as metadata. === "method.ts" @@ -446,6 +446,25 @@ Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` } ``` +=== "middy.ts" + + ```typescript hl_lines="14" + import { Tracer, captureLambdaHandler } from '@aws-lambda-powertools/tracer'; + import middy from '@middy/core'; + + const tracer = new Tracer({ serviceName: 'serverlessAirline' }); + + const lambdaHandler = async (_event: any, _context: any): Promise => { + /* ... */ + }; + + // Wrap the handler with middy + export const handler = middy(lambdaHandler) + // Use the middleware by passing the Tracer instance as a parameter, + // but specify the captureResponse option as false. + .use(captureLambdaHandler(tracer, { captureResponse: false })); + ``` + ### Disabling exception auto-capture Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct Tracer **not** to serialize exceptions as metadata. diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index bac2490177..9cb32cf332 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -2,8 +2,12 @@ import type middy from '@middy/core'; import type { Tracer } from '../Tracer'; import type { Segment, Subsegment } from 'aws-xray-sdk-core'; +export type CaptureLambdaHandlerOptions = { + captureResponse?: boolean +}; + /** - * A middy middleware automating capture of metadata and annotations on segments or subsegments ofr a Lambda Handler. + * A middy middleware automating capture of metadata and annotations on segments or subsegments for a Lambda Handler. * * Using this middleware on your handler function will automatically: * * handle the subsegment lifecycle @@ -26,7 +30,7 @@ import type { Segment, Subsegment } from 'aws-xray-sdk-core'; * @param target - The Tracer instance to use for tracing * @returns middleware object - The middy middleware object */ -const captureLambdaHandler = (target: Tracer): middy.MiddlewareObj => { +const captureLambdaHandler = (target: Tracer, options?: CaptureLambdaHandlerOptions): middy.MiddlewareObj => { let lambdaSegment: Subsegment | Segment; const open = (): void => { @@ -51,7 +55,9 @@ const captureLambdaHandler = (target: Tracer): middy.MiddlewareObj => { const captureLambdaHandlerAfter = async (request: middy.Request): Promise => { if (target.isTracingEnabled()) { - target.addResponseAsMetadata(request.response, process.env._HANDLER); + if (options?.captureResponse ?? true) { + target.addResponseAsMetadata(request.response, process.env._HANDLER); + } close(); } }; diff --git a/packages/tracer/tests/unit/middy.test.ts b/packages/tracer/tests/unit/middy.test.ts index 55b72dde7a..abddaee2c7 100644 --- a/packages/tracer/tests/unit/middy.test.ts +++ b/packages/tracer/tests/unit/middy.test.ts @@ -109,6 +109,28 @@ describe('Middy middleware', () => { }); + test('when used while captureResponse set to false, it does not capture the response as metadata', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); + const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + foo: 'bar' + }); + const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer, { captureResponse: false })); + + // Act + await handler({}, context, () => console.log('Lambda invoked!')); + + // Assess + expect(setSegmentSpy).toHaveBeenCalledTimes(2); + expect('metadata' in newSubsegment).toBe(false); + + }); + test('when used with standard config, it captures the response as metadata', async () => { // Prepare From dd42e52ff97fde280bb5b04c14f9867a309d3872 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Fri, 19 Aug 2022 01:38:51 +0000 Subject: [PATCH 07/10] test: add middy options e2e --- .../allFeatures.middy.test.functionCode.ts | 8 ++- .../tests/e2e/allFeatures.middy.test.ts | 72 +++++++++++++++++++ packages/tracer/tests/helpers/tracesUtils.ts | 3 +- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/packages/tracer/tests/e2e/allFeatures.middy.test.functionCode.ts b/packages/tracer/tests/e2e/allFeatures.middy.test.functionCode.ts index 47c58be13e..a5b3f35ea5 100644 --- a/packages/tracer/tests/e2e/allFeatures.middy.test.functionCode.ts +++ b/packages/tracer/tests/e2e/allFeatures.middy.test.functionCode.ts @@ -36,7 +36,7 @@ const refreshAWSSDKImport = (): void => { const tracer = new Tracer({ serviceName: serviceName }); const dynamoDBv3 = tracer.captureAWSv3Client(new DynamoDBClient({})); -export const handler = middy(async (event: CustomEvent, _context: Context): Promise => { +const testHandler = async (event: CustomEvent, _context: Context): Promise => { tracer.putAnnotation('invocation', event.invocation); tracer.putAnnotation(customAnnotationKey, customAnnotationValue); tracer.putMetadata(customMetadataKey, customMetadataValue); @@ -63,4 +63,8 @@ export const handler = middy(async (event: CustomEvent, _context: Context): Prom } catch (err) { throw err; } -}).use(captureLambdaHandler(tracer)); \ No newline at end of file +}; + +export const handler = middy(testHandler).use(captureLambdaHandler(tracer)); + +export const handlerWithNoCaptureResponseViaMiddlewareOption = middy(testHandler).use(captureLambdaHandler(tracer, { captureResponse: false })); \ No newline at end of file diff --git a/packages/tracer/tests/e2e/allFeatures.middy.test.ts b/packages/tracer/tests/e2e/allFeatures.middy.test.ts index 2f4b7cf931..ba314268a5 100644 --- a/packages/tracer/tests/e2e/allFeatures.middy.test.ts +++ b/packages/tracer/tests/e2e/allFeatures.middy.test.ts @@ -78,6 +78,13 @@ const uuidFunction3 = v4(); const functionNameWithTracerDisabled = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction3, runtime, 'AllFeatures-Middy-TracerDisabled'); const serviceNameWithTracerDisabled = functionNameWithNoCaptureErrorOrResponse; +/** + * Function #4 doesn't capture response + */ +const uuidFunction4 = v4(); +const functionNameWithNoCaptureResponseViaMiddlewareOption = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction4, runtime, 'AllFeatures-Middy-NoCaptureResponse2'); +const serviceNameWithNoCaptureResponseViaMiddlewareOption = functionNameWithNoCaptureResponseViaMiddlewareOption; + const xray = new AWS.XRay(); const invocations = 3; @@ -149,6 +156,22 @@ describe(`Tracer E2E tests, all features with middy instantiation for runtime: $ }); ddbTable.grantWriteData(functionWithTracerDisabled); + const functionThatDoesNotCaptureResponseViaMiddlewareOption = createTracerTestFunction({ + stack, + functionName: functionNameWithNoCaptureResponseViaMiddlewareOption, + entry, + handler: 'handlerWithNoCaptureResponseViaMiddlewareOption', + expectedServiceName: serviceNameWithNoCaptureResponseViaMiddlewareOption, + environmentParams: { + TEST_TABLE_NAME: ddbTableName, + POWERTOOLS_TRACER_CAPTURE_RESPONSE: 'true', + POWERTOOLS_TRACER_CAPTURE_ERROR: 'true', + POWERTOOLS_TRACE_ENABLED: 'true', + }, + runtime + }); + ddbTable.grantWriteData(functionThatDoesNotCaptureResponseViaMiddlewareOption); + await deployStack(integTestApp, stack); // Act @@ -156,6 +179,7 @@ describe(`Tracer E2E tests, all features with middy instantiation for runtime: $ invokeAllTestCases(functionNameWithAllFlagsEnabled), invokeAllTestCases(functionNameWithNoCaptureErrorOrResponse), invokeAllTestCases(functionNameWithTracerDisabled), + invokeAllTestCases(functionNameWithNoCaptureResponseViaMiddlewareOption), ]); }, SETUP_TIMEOUT); @@ -299,6 +323,54 @@ describe(`Tracer E2E tests, all features with middy instantiation for runtime: $ }, TEST_CASE_TIMEOUT); + it('should not capture response when the middleware\'s captureResponse is set to false', async () => { + + const tracesWithNoCaptureResponse = await getTraces(xray, startTime, await getFunctionArn(functionNameWithNoCaptureResponseViaMiddlewareOption), invocations, 5); + + expect(tracesWithNoCaptureResponse.length).toBe(invocations); + + // Assess + for (let i = 0; i < invocations; i++) { + const trace = tracesWithNoCaptureResponse[i]; + + /** + * Expect the trace to have 5 segments: + * 1. Lambda Context (AWS::Lambda) + * 2. Lambda Function (AWS::Lambda::Function) + * 3. DynamoDB (AWS::DynamoDB) + * 4. DynamoDB Table (AWS::DynamoDB::Table) + * 5. Remote call (httpbin.org) + */ + expect(trace.Segments.length).toBe(5); + const invocationSubsegment = getInvocationSubsegment(trace); + + /** + * Invocation subsegment should have a subsegment '## index.handlerWithNoCaptureResponseViaMiddlewareOption' (default behavior for PowerTool tracer) + * '## index.handlerWithNoCaptureResponseViaMiddlewareOption' subsegment should have 3 subsegments + * 1. DynamoDB (PutItem on the table) + * 2. DynamoDB (PutItem overhead) + * 3. httpbin.org (Remote call) + */ + const handlerSubsegment = getFirstSubsegment(invocationSubsegment); + expect(handlerSubsegment.name).toBe('## index.handlerWithNoCaptureResponseViaMiddlewareOption'); + expect(handlerSubsegment?.subsegments).toHaveLength(3); + + if (!handlerSubsegment.subsegments) { + fail('"## index.handlerWithNoCaptureResponseViaMiddlewareOption" subsegment should have subsegments'); + } + const subsegments = splitSegmentsByName(handlerSubsegment.subsegments, [ 'DynamoDB', 'httpbin.org' ]); + expect(subsegments.get('DynamoDB')?.length).toBe(2); + expect(subsegments.get('httpbin.org')?.length).toBe(1); + expect(subsegments.get('other')?.length).toBe(0); + + const shouldThrowAnError = (i === (invocations - 1)); + if (shouldThrowAnError) { + assertErrorAndFault(invocationSubsegment, expectedCustomErrorMessage); + } + } + + }, TEST_CASE_TIMEOUT); + it('should not capture any custom traces when disabled', async () => { const expectedNoOfTraces = 2; const tracesWithTracerDisabled = await getTraces(xray, startTime, await getFunctionArn(functionNameWithTracerDisabled), invocations, expectedNoOfTraces); diff --git a/packages/tracer/tests/helpers/tracesUtils.ts b/packages/tracer/tests/helpers/tracesUtils.ts index 117b05501c..f41b744ad5 100644 --- a/packages/tracer/tests/helpers/tracesUtils.ts +++ b/packages/tracer/tests/helpers/tracesUtils.ts @@ -79,6 +79,7 @@ export interface ParsedTrace { interface TracerTestFunctionParams { stack: Stack functionName: string + handler?: string entry: string expectedServiceName: string environmentParams: { [key: string]: string } @@ -237,7 +238,7 @@ const createTracerTestFunction = (params: TracerTestFunctionParams): NodejsFunct const func = new NodejsFunction(stack, functionName, { entry: entry, functionName: functionName, - handler: 'handler', + handler: params.handler ?? 'handler', tracing: Tracing.ACTIVE, architecture: Architecture.X86_64, memorySize: 256, // Default value (128) will take too long to process From cb4de92c8b4e06001e8d9bd0c9485c781159778c Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Fri, 19 Aug 2022 02:22:48 +0000 Subject: [PATCH 08/10] test: add decorator e2e --- ...allFeatures.decorator.test.functionCode.ts | 41 +++++++++- .../tests/e2e/allFeatures.decorator.test.ts | 80 +++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts b/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts index 000faf567e..337aec311d 100644 --- a/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts +++ b/packages/tracer/tests/e2e/allFeatures.decorator.test.functionCode.ts @@ -35,14 +35,13 @@ const refreshAWSSDKImport = (): void => { const tracer = new Tracer({ serviceName: serviceName }); const dynamoDBv3 = tracer.captureAWSv3Client(new DynamoDBClient({})); -export class MyFunctionWithDecorator { +export class MyFunctionBase { private readonly returnValue: string; public constructor() { this.returnValue = customResponseValue; } - @tracer.captureLambdaHandler() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore public handler(event: CustomEvent, _context: Context, _callback: Callback): void | Promise { @@ -79,7 +78,6 @@ export class MyFunctionWithDecorator { }); } - @tracer.captureMethod() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore public myMethod(): string { @@ -87,5 +85,40 @@ export class MyFunctionWithDecorator { } } +class MyFunctionWithDecorator extends MyFunctionBase { + @tracer.captureLambdaHandler() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler(event: CustomEvent, _context: Context, _callback: Callback): void | Promise { + return super.handler(event, _context, _callback); + } + + @tracer.captureMethod() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public myMethod(): string { + return super.myMethod(); + } +} + const handlerClass = new MyFunctionWithDecorator(); -export const handler = handlerClass.handler.bind(handlerClass); \ No newline at end of file +export const handler = handlerClass.handler.bind(handlerClass); + +class MyFunctionWithDecoratorCaptureResponseFalse extends MyFunctionBase { + @tracer.captureLambdaHandler({ captureResponse: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler(event: CustomEvent, _context: Context, _callback: Callback): void | Promise { + return super.handler(event, _context, _callback); + } + + @tracer.captureMethod({ captureResponse: false }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public myMethod(): string { + return super.myMethod(); + } +} + +const handlerWithCaptureResponseFalseClass = new MyFunctionWithDecoratorCaptureResponseFalse(); +export const handlerWithCaptureResponseFalse = handlerClass.handler.bind(handlerWithCaptureResponseFalseClass); \ No newline at end of file diff --git a/packages/tracer/tests/e2e/allFeatures.decorator.test.ts b/packages/tracer/tests/e2e/allFeatures.decorator.test.ts index 5c2272f18a..befe56830b 100644 --- a/packages/tracer/tests/e2e/allFeatures.decorator.test.ts +++ b/packages/tracer/tests/e2e/allFeatures.decorator.test.ts @@ -78,6 +78,13 @@ const uuidFunction3 = v4(); const functionNameWithTracerDisabled = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction3, runtime, 'AllFeatures-Decorator-TracerDisabled'); const serviceNameWithTracerDisabled = functionNameWithNoCaptureErrorOrResponse; +/** + * Function #4 disables tracer + */ +const uuidFunction4 = v4(); +const functionNameWithCaptureResponseFalse = generateUniqueName(RESOURCE_NAME_PREFIX, uuidFunction4, runtime, 'AllFeatures-Decorator-CaptureResponseFalse'); +const serviceNameWithCaptureResponseFalse = functionNameWithCaptureResponseFalse; + const xray = new AWS.XRay(); const invocations = 3; @@ -149,6 +156,22 @@ describe(`Tracer E2E tests, all features with decorator instantiation for runtim }); ddbTable.grantWriteData(functionWithTracerDisabled); + const functionWithCaptureResponseFalse = createTracerTestFunction({ + stack, + functionName: functionNameWithCaptureResponseFalse, + handler: 'handlerWithCaptureResponseFalse', + entry, + expectedServiceName: serviceNameWithCaptureResponseFalse, + environmentParams: { + TEST_TABLE_NAME: ddbTableName, + POWERTOOLS_TRACER_CAPTURE_RESPONSE: 'true', + POWERTOOLS_TRACER_CAPTURE_ERROR: 'true', + POWERTOOLS_TRACE_ENABLED: 'true', + }, + runtime + }); + ddbTable.grantWriteData(functionWithCaptureResponseFalse); + await deployStack(integTestApp, stack); // Act @@ -156,6 +179,7 @@ describe(`Tracer E2E tests, all features with decorator instantiation for runtim invokeAllTestCases(functionNameWithAllFlagsEnabled), invokeAllTestCases(functionNameWithNoCaptureErrorOrResponse), invokeAllTestCases(functionNameWithTracerDisabled), + invokeAllTestCases(functionNameWithCaptureResponseFalse), ]); }, SETUP_TIMEOUT); @@ -303,6 +327,62 @@ describe(`Tracer E2E tests, all features with decorator instantiation for runtim }, TEST_CASE_TIMEOUT); + it('should not capture response when the decorator\'s captureResponse is set to false', async () => { + + const tracesWithCaptureResponseFalse = await getTraces(xray, startTime, await getFunctionArn(functionNameWithCaptureResponseFalse), invocations, 5); + + expect(tracesWithCaptureResponseFalse.length).toBe(invocations); + + // Assess + for (let i = 0; i < invocations; i++) { + const trace = tracesWithCaptureResponseFalse[i]; + + /** + * Expect the trace to have 5 segments: + * 1. Lambda Context (AWS::Lambda) + * 2. Lambda Function (AWS::Lambda::Function) + * 3. DynamoDB (AWS::DynamoDB) + * 4. DynamoDB Table (AWS::DynamoDB::Table) + * 5. Remote call (httpbin.org) + */ + expect(trace.Segments.length).toBe(5); + const invocationSubsegment = getInvocationSubsegment(trace); + + /** + * Invocation subsegment should have a subsegment '## index.handler' (default behavior for PowerTool tracer) + * '## index.handler' subsegment should have 4 subsegments + * 1. DynamoDB (PutItem on the table) + * 2. DynamoDB (PutItem overhead) + * 3. httpbin.org (Remote call) + * 4. '### myMethod' (method decorator) + */ + const handlerSubsegment = getFirstSubsegment(invocationSubsegment); + expect(handlerSubsegment.name).toBe('## index.handlerWithCaptureResponseFalse'); + expect(handlerSubsegment?.subsegments).toHaveLength(4); + + if (!handlerSubsegment.subsegments) { + fail('"## index.handlerWithCaptureResponseFalse" subsegment should have subsegments'); + } + const subsegments = splitSegmentsByName(handlerSubsegment.subsegments, [ 'DynamoDB', 'httpbin.org', '### myMethod' ]); + expect(subsegments.get('DynamoDB')?.length).toBe(2); + expect(subsegments.get('httpbin.org')?.length).toBe(1); + expect(subsegments.get('### myMethod')?.length).toBe(1); + expect(subsegments.get('other')?.length).toBe(0); + + // No metadata because capturing the response was disabled and that's + // the only metadata that could be in the subsegment for the test. + const myMethodSegment = subsegments.get('### myMethod')?.[0]; + expect(myMethodSegment).toBeDefined(); + expect(myMethodSegment).not.toHaveProperty('metadata'); + + const shouldThrowAnError = (i === (invocations - 1)); + if (shouldThrowAnError) { + assertErrorAndFault(invocationSubsegment, expectedCustomErrorMessage); + } + } + + }, TEST_CASE_TIMEOUT); + it('should not capture any custom traces when disabled', async () => { const expectedNoOfTraces = 2; const tracesWithTracerDisabled = await getTraces(xray, startTime, await getFunctionArn(functionNameWithTracerDisabled), invocations, expectedNoOfTraces); From 2c84e5f7ec17c68a7cca55e21cebd9f723bf3e36 Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Fri, 19 Aug 2022 16:55:16 +0000 Subject: [PATCH 09/10] refactor: initial review changes --- docs/core/tracer.md | 30 +++++--- packages/tracer/src/Tracer.ts | 8 +-- packages/tracer/src/middleware/middy.ts | 7 +- packages/tracer/src/types/Tracer.ts | 32 ++------- packages/tracer/tests/unit/Tracer.test.ts | 85 +++++++++++++++++++++++ packages/tracer/tests/unit/middy.test.ts | 31 +++++++++ 6 files changed, 149 insertions(+), 44 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index f938f617a4..fe9c0c31fd 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -256,7 +256,7 @@ You can trace other Class methods using the `captureMethod` decorator or any arb } const handlerClass = new Lambda(); - export const handler = myFunction.handler.bind(handlerClass); // (1) + export const handler = handlerClass.handler.bind(handlerClass); // (1) ``` 1. Binding your handler method allows your handler to access `this`. @@ -412,38 +412,48 @@ Use **`POWERTOOLS_TRACER_CAPTURE_RESPONSE=false`** environment variable to instr 2. You might manipulate **streaming objects that can be read only once**; this prevents subsequent calls from being empty 3. You might return **more than 64K** of data _e.g., `message too long` error_ -### Disabling response capture for targeted methods and handlers - -Use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators, or use the same option in the middy `captureLambdaHander` middleware to instruct Tracer **not** to serialize function responses as metadata. +Alternatively, use the `captureResponse: false` option in both `tracer.captureLambdaHandler()` and `tracer.captureMethod()` decorators, or use the same option in the Middy `captureLambdaHander` middleware to instruct Tracer **not** to serialize function responses as metadata. === "method.ts" - ```typescript hl_lines="5" + ```typescript hl_lines="6" import { Tracer } from '@aws-lambda-powertools/tracer'; const tracer = new Tracer({ serviceName: 'serverlessAirline' }); - class MyThing { - @tracer.captureMethod({ captureResponse: false }) - myMethod(): string { + + class Lambda implements LambdaInterface { + @tracer.captureMethod({ captureResult: false }) + public getChargeId(): string { /* ... */ return 'foo bar'; } + + public async handler(_event: any, _context: any): Promise { + /* ... */ + } } + + const handlerClass = new Lambda(); + export const handler = handlerClass.handler.bind(handlerClass); ``` === "handler.ts" - ```typescript hl_lines="6" + ```typescript hl_lines="7" import { Tracer } from '@aws-lambda-powertools/tracer'; import { LambdaInterface } from '@aws-lambda-powertools/commons'; const tracer = new Tracer({ serviceName: 'serverlessAirline' }); - class MyHandler implements LambdaInterface { + + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler({ captureResponse: false }) async handler(_event: any, _context: any): Promise { /* ... */ } } + + const handlerClass = new Lambda(); + export const handler = handlerClass.handler.bind(handlerClass); ``` === "middy.ts" diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 0fc163eccd..e7663b2673 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -2,7 +2,7 @@ import { Handler } from 'aws-lambda'; import { AsyncHandler, SyncHandler, Utility } from '@aws-lambda-powertools/commons'; import { TracerInterface } from '.'; import { ConfigServiceInterface, EnvironmentVariablesService } from './config'; -import { HandlerMethodDecorator, TracerOptions, TracerCaptureMethodOptions, TracerCaptureLambdaHandlerOptions, MethodDecorator } from './types'; +import { HandlerMethodDecorator, TracerOptions, HandlerOptions, MethodDecorator } from './types'; import { ProviderService, ProviderServiceInterface } from './provider'; import { Segment, Subsegment } from 'aws-xray-sdk-core'; @@ -339,7 +339,7 @@ class Tracer extends Utility implements TracerInterface { * * @decorator Class */ - public captureLambdaHandler(options?: TracerCaptureLambdaHandlerOptions): HandlerMethodDecorator { + public captureLambdaHandler(options?: HandlerOptions): HandlerMethodDecorator { return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. @@ -419,7 +419,7 @@ class Tracer extends Utility implements TracerInterface { * * @decorator Class */ - public captureMethod(options?: TracerCaptureMethodOptions): MethodDecorator { + public captureMethod(options?: HandlerOptions): MethodDecorator { 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 @@ -441,9 +441,9 @@ class Tracer extends Utility implements TracerInterface { if (options?.captureResponse ?? true) { tracerRef.addResponseAsMetadata(result, originalMethod.name); } - } catch (error) { tracerRef.addErrorAsMetadata(error as Error); + throw error; } finally { subsegment?.close(); diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index 9cb32cf332..e4ad501f73 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -1,10 +1,7 @@ import type middy from '@middy/core'; import type { Tracer } from '../Tracer'; import type { Segment, Subsegment } from 'aws-xray-sdk-core'; - -export type CaptureLambdaHandlerOptions = { - captureResponse?: boolean -}; +import { HandlerOptions } from '../types'; /** * A middy middleware automating capture of metadata and annotations on segments or subsegments for a Lambda Handler. @@ -30,7 +27,7 @@ export type CaptureLambdaHandlerOptions = { * @param target - The Tracer instance to use for tracing * @returns middleware object - The middy middleware object */ -const captureLambdaHandler = (target: Tracer, options?: CaptureLambdaHandlerOptions): middy.MiddlewareObj => { +const captureLambdaHandler = (target: Tracer, options?: HandlerOptions): middy.MiddlewareObj => { let lambdaSegment: Subsegment | Segment; const open = (): void => { diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index ce0ba2e31e..9003b1e997 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -27,40 +27,23 @@ type TracerOptions = { }; /** - * Options for the captureMethod decorator to be used when decorating a method. + * Options for handler decorators and middleware. * * Usage: * @example * ```typescript * const tracer = new Tracer(); * - * class MyThing { - * @tracer.captureMethod({ captureResponse: false }) - * myMethod(): string { - * return 'foo bar'; - * } - * } - * ``` - */ -type TracerCaptureMethodOptions = { - captureResponse?: boolean -}; - -/** - * Options for the captureLambdaHandler decorator to be used when decorating a method. - * - * Usage: - * @example - * ```typescript - * const tracer = new Tracer(); - * - * class MyThing implements LambdaInterface { + * class Lambda implements LambdaInterface { * @tracer.captureLambdaHandler({ captureResponse: false }) * async handler(_event: any, _context: any): Promise {} * } + * + * const handlerClass = new Lambda(); + * export const handler = handlerClass.handler.bind(handlerClass); * ``` */ -type TracerCaptureLambdaHandlerOptions = { +type HandlerOptions = { captureResponse?: boolean }; @@ -76,8 +59,7 @@ type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: T export { TracerOptions, - TracerCaptureLambdaHandlerOptions, - TracerCaptureMethodOptions, + HandlerOptions, HandlerMethodDecorator, MethodDecorator }; \ No newline at end of file diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 03cae07d8d..4e77ebf490 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -678,6 +678,47 @@ describe('Class: Tracer', () => { }); + test('when used as decorator while captureResponse is set to true, it captures the response as metadata', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + class Lambda implements LambdaInterface { + + @tracer.captureLambdaHandler({ captureResponse: true }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler(_event: TEvent, _context: Context, _callback: Callback): void | Promise { + return new Promise((resolve, _reject) => resolve({ + foo: 'bar' + } as unknown as TResult)); + } + + } + + // Act + await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); + expect(newSubsegment).toEqual(expect.objectContaining({ + name: '## index.handler', + metadata: { + 'hello-world': { + 'index.handler response': { + foo: 'bar', + }, + }, + } + })); + + }); + test('when used as decorator and with standard config, it captures the response as metadata', async () => { // Prepare @@ -1040,6 +1081,50 @@ describe('Class: Tracer', () => { }); + test('when used as decorator and with captureResponse set to true, it does captures the response as metadata', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod'); + jest.spyOn(newSubsegment, 'flush').mockImplementation(() => null); + jest.spyOn(tracer.provider, 'getSegment') + .mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + class Lambda implements LambdaInterface { + + @tracer.captureMethod() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public async dummyMethod(some: string): Promise { + return new Promise((resolve, _reject) => setTimeout(() => resolve(some), 3000)); + } + + public async handler(_event: TEvent, _context: Context, _callback: Callback): Promise { + const result = await this.dummyMethod('foo bar'); + + return new Promise((resolve, _reject) => resolve(result as unknown as TResult)); + } + + } + + // Act + await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); + + // Assess + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); + expect(captureAsyncFuncSpy).toHaveBeenCalledWith('### dummyMethod', expect.anything()); + expect(newSubsegment).toEqual(expect.objectContaining({ + name: '### dummyMethod', + metadata: { + 'hello-world': { + 'dummyMethod response': 'foo bar', + }, + } + })); + + }); + test('when used as decorator and with standard config, it captures the exception correctly', async () => { // Prepare diff --git a/packages/tracer/tests/unit/middy.test.ts b/packages/tracer/tests/unit/middy.test.ts index abddaee2c7..f1b33b01be 100644 --- a/packages/tracer/tests/unit/middy.test.ts +++ b/packages/tracer/tests/unit/middy.test.ts @@ -131,6 +131,37 @@ describe('Middy middleware', () => { }); + test('when used while captureResponse set to true, it captures the response as metadata', async () => { + + // Prepare + const tracer: Tracer = new Tracer(); + const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); + const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment); + setContextMissingStrategy(() => null); + const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + foo: 'bar' + }); + const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer, { captureResponse: true })); + + // Act + await handler({}, context, () => console.log('Lambda invoked!')); + + // Assess + expect(setSegmentSpy).toHaveBeenCalledTimes(2); + expect(newSubsegment).toEqual(expect.objectContaining({ + name: '## index.handler', + metadata: { + 'hello-world': { + 'index.handler response': { + foo: 'bar', + }, + }, + } + })); + + }); + test('when used with standard config, it captures the response as metadata', async () => { // Prepare From 9597970eebd2250bd31a8fd58421e6b4fb5f105a Mon Sep 17 00:00:00 2001 From: Josh Kellendonk Date: Mon, 22 Aug 2022 09:36:53 -0600 Subject: [PATCH 10/10] refactor: apply import change from review Co-authored-by: Andrea Amorosi --- packages/tracer/src/middleware/middy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index e4ad501f73..0774e02fda 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -1,7 +1,7 @@ import type middy from '@middy/core'; import type { Tracer } from '../Tracer'; import type { Segment, Subsegment } from 'aws-xray-sdk-core'; -import { HandlerOptions } from '../types'; +import type { HandlerOptions } from '../types'; /** * A middy middleware automating capture of metadata and annotations on segments or subsegments for a Lambda Handler.