From a82fc3a89b8512f7ff5c0bfd4f27cd59e33f10f4 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 20 Mar 2023 10:01:04 +0100 Subject: [PATCH] refactor(tracer): log warning instead of throwing when segment is not found (#1370) * improv: moved logic to provider + changed middleware behavior around absent segment * tests: fixed tests for middleware * tests: fixed tests for main class * tests: fixed provider service tests * chore: bump xray-sdk-core * chore: update jsdoc for addErrorAsMetadata * chore: update jsdoc for getSegment * chore: update jsdoc for getSegment --- package-lock.json | 16 +- packages/tracer/package.json | 4 +- packages/tracer/src/Tracer.ts | 40 +- packages/tracer/src/TracerInterface.ts | 4 +- packages/tracer/src/middleware/middy.ts | 20 +- .../tracer/src/provider/ProviderService.ts | 53 ++- .../src/provider/ProviderServiceInterface.ts | 4 + .../tracer/tests/unit/ProviderService.test.ts | 142 +++++- packages/tracer/tests/unit/Tracer.test.ts | 405 +++++------------- packages/tracer/tests/unit/middy.test.ts | 169 +++----- 10 files changed, 391 insertions(+), 466 deletions(-) diff --git a/package-lock.json b/package-lock.json index 104f681567..c21c3e3d9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5815,9 +5815,9 @@ } }, "node_modules/aws-xray-sdk-core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.0.tgz", - "integrity": "sha512-PH3jqjUp8fTIaRa0i6fjJxXJSR9yDwldH1Qw7nMDNAouL7txsrSM6BhrQEjcaZ2mwfU0PNx8tsFQ+2/nfNdR1w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.1.tgz", + "integrity": "sha512-HPQ+9GMF+yhDDXNKPp6HGAIv2yVapKTI7WRs2aN3TT+RxQkbmgO+3JlcMvRL2/lu0RqTVWoYCIhmF9/H/zRL1A==", "dependencies": { "@aws-sdk/service-error-classification": "^3.4.1", "@aws-sdk/types": "^3.4.1", @@ -16792,7 +16792,7 @@ "license": "MIT-0", "dependencies": { "@aws-lambda-powertools/commons": "^1.6.0", - "aws-xray-sdk-core": "^3.4.0" + "aws-xray-sdk-core": "^3.4.1" }, "devDependencies": { "@aws-sdk/client-dynamodb": "^3.231.0", @@ -17081,7 +17081,7 @@ "@aws-sdk/client-dynamodb": "^3.231.0", "@types/promise-retry": "^1.1.3", "aws-sdk": "^2.1276.0", - "aws-xray-sdk-core": "^3.4.0", + "aws-xray-sdk-core": "3.4.1", "axios": "^1.2.1", "promise-retry": "^2.0.1" } @@ -21326,9 +21326,9 @@ "dev": true }, "aws-xray-sdk-core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.0.tgz", - "integrity": "sha512-PH3jqjUp8fTIaRa0i6fjJxXJSR9yDwldH1Qw7nMDNAouL7txsrSM6BhrQEjcaZ2mwfU0PNx8tsFQ+2/nfNdR1w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.4.1.tgz", + "integrity": "sha512-HPQ+9GMF+yhDDXNKPp6HGAIv2yVapKTI7WRs2aN3TT+RxQkbmgO+3JlcMvRL2/lu0RqTVWoYCIhmF9/H/zRL1A==", "requires": { "@aws-sdk/service-error-classification": "^3.4.1", "@aws-sdk/types": "^3.4.1", diff --git a/packages/tracer/package.json b/packages/tracer/package.json index 714feea5c5..e7ec65f99b 100644 --- a/packages/tracer/package.json +++ b/packages/tracer/package.json @@ -52,7 +52,7 @@ }, "dependencies": { "@aws-lambda-powertools/commons": "^1.6.0", - "aws-xray-sdk-core": "^3.4.0" + "aws-xray-sdk-core": "^3.4.1" }, "keywords": [ "aws", @@ -63,4 +63,4 @@ "serverless", "nodejs" ] -} \ No newline at end of file +} diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 6d846b371f..b4fe242395 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -38,7 +38,7 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; * * const tracer = new Tracer({ serviceName: 'serverlessAirline' }); * -* const lambdaHandler = async (_event: any, _context: any) => { + * const lambdaHandler = async (_event: any, _context: any) => { * ... * }; * @@ -153,20 +153,26 @@ class Tracer extends Utility implements TracerInterface { * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-errors * * @param error - Error to serialize as metadata + * @param [remote] - Whether the error was thrown by a remote service. Defaults to `false` */ - public addErrorAsMetadata(error: Error): void { + public addErrorAsMetadata(error: Error, remote?: boolean): void { if (!this.isTracingEnabled()) { return; } const subsegment = this.getSegment(); + if (subsegment === undefined) { + + return; + } + if (!this.captureError) { subsegment.addErrorFlag(); return; } - subsegment.addError(error, false); + subsegment.addError(error, remote || false); } /** @@ -506,7 +512,7 @@ class Tracer extends Utility implements TracerInterface { } /** - * Get the active segment or subsegment in the current scope. + * Get the active segment or subsegment (if any) in the current scope. * * Usually you won't need to call this method unless you are creating custom subsegments or using manual mode. * @@ -525,15 +531,17 @@ class Tracer extends Utility implements TracerInterface { * } * ``` * - * @returns segment - The active segment or subsegment in the current scope. + * @returns The active segment or subsegment in the current scope. Will log a warning and return `undefined` if no segment is found. */ - public getSegment(): Segment | Subsegment { + public getSegment(): Segment | Subsegment | undefined { if (!this.isTracingEnabled()) { return new Subsegment('## Dummy segment'); } const segment = this.provider.getSegment(); if (segment === undefined) { - throw new Error('Failed to get the current sub/segment from the context.'); + console.warn( + 'Failed to get the current sub/segment from the context, this is likely because you are not using the Tracer in a Lambda function.' + ); } return segment; @@ -573,13 +581,7 @@ class Tracer extends Utility implements TracerInterface { public putAnnotation(key: string, value: string | number | boolean): void { if (!this.isTracingEnabled()) return; - const document = this.getSegment(); - if (document instanceof Segment) { - console.warn('You cannot annotate the main segment in a Lambda execution environment'); - - return; - } - document.addAnnotation(key, value); + this.provider.putAnnotation(key, value); } /** @@ -606,15 +608,7 @@ class Tracer extends Utility implements TracerInterface { public putMetadata(key: string, value: unknown, namespace?: string | undefined): void { if (!this.isTracingEnabled()) return; - const document = this.getSegment(); - if (document instanceof Segment) { - console.warn('You cannot add metadata to the main segment in a Lambda execution environment'); - - return; - } - - namespace = namespace || this.serviceName; - document.addMetadata(key, value, namespace); + this.provider.putMetadata(key, value, namespace || this.serviceName); } /** diff --git a/packages/tracer/src/TracerInterface.ts b/packages/tracer/src/TracerInterface.ts index 128a2c5553..344e44177e 100644 --- a/packages/tracer/src/TracerInterface.ts +++ b/packages/tracer/src/TracerInterface.ts @@ -2,7 +2,7 @@ import { CaptureLambdaHandlerOptions, CaptureMethodOptions, HandlerMethodDecorat import { Segment, Subsegment } from 'aws-xray-sdk-core'; interface TracerInterface { - addErrorAsMetadata(error: Error): void + addErrorAsMetadata(error: Error, remote?: boolean): void addResponseAsMetadata(data?: unknown, methodName?: string): void addServiceNameAnnotation(): void annotateColdStart(): void @@ -11,7 +11,7 @@ interface TracerInterface { captureAWSClient(service: T): void | T captureLambdaHandler(options?: CaptureLambdaHandlerOptions): HandlerMethodDecorator captureMethod(options?: CaptureMethodOptions): MethodDecorator - getSegment(): Segment | Subsegment + getSegment(): Segment | Subsegment | undefined getRootXrayTraceId(): string | undefined isTracingEnabled(): boolean putAnnotation: (key: string, value: string | number | boolean) => void diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index a5c8e81b6c..399945c453 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -34,18 +34,26 @@ import type { * @returns middleware - The middy middleware object */ const captureLambdaHandler = (target: Tracer, options?: CaptureLambdaHandlerOptions): MiddlewareLikeObj => { - let lambdaSegment: Subsegment | Segment; + let lambdaSegment: Segment; + let handlerSegment: Subsegment; const open = (): void => { - lambdaSegment = target.getSegment(); - const handlerSegment = lambdaSegment.addNewSubsegment(`## ${process.env._HANDLER}`); + const segment = target.getSegment(); + if (segment === undefined) { + return; + } + // If segment is defined, then it is a Segment as this middleware is only used for Lambda Handlers + lambdaSegment = segment as Segment; + handlerSegment = lambdaSegment.addNewSubsegment(`## ${process.env._HANDLER}`); target.setSegment(handlerSegment); }; const close = (): void => { - const subsegment = target.getSegment(); - subsegment.close(); - target.setSegment(lambdaSegment as Segment); + if (handlerSegment === undefined || lambdaSegment === null) { + return; + } + handlerSegment.close(); + target.setSegment(lambdaSegment); }; const captureLambdaHandlerBefore = async (): Promise => { diff --git a/packages/tracer/src/provider/ProviderService.ts b/packages/tracer/src/provider/ProviderService.ts index 626e9b5585..56abd43dfd 100644 --- a/packages/tracer/src/provider/ProviderService.ts +++ b/packages/tracer/src/provider/ProviderService.ts @@ -1,10 +1,27 @@ -import { ContextMissingStrategy } from 'aws-xray-sdk-core/dist/lib/context_utils'; +import { + ContextMissingStrategy +} from 'aws-xray-sdk-core/dist/lib/context_utils'; import { Namespace } from 'cls-hooked'; import { ProviderServiceInterface } from '.'; -import { captureAWS, captureAWSClient, captureAWSv3Client, captureAsyncFunc, captureFunc, captureHTTPsGlobal, getNamespace, getSegment, setSegment, Segment, Subsegment, setContextMissingStrategy, setDaemonAddress, setLogger, Logger } from 'aws-xray-sdk-core'; +import { + captureAWS, + captureAWSClient, + captureAWSv3Client, + captureAsyncFunc, + captureFunc, + captureHTTPsGlobal, + getNamespace, + getSegment, + setSegment, + Segment, + Subsegment, + setContextMissingStrategy, + setDaemonAddress, + setLogger, + Logger +} from 'aws-xray-sdk-core'; class ProviderService implements ProviderServiceInterface { - public captureAWS(awssdk: T): T { return captureAWS(awssdk); } @@ -42,6 +59,36 @@ class ProviderService implements ProviderServiceInterface { return getSegment(); } + public putAnnotation(key: string, value: string | number | boolean): void { + const segment = this.getSegment(); + if (segment === undefined) { + console.warn('No active segment or subsegment found, skipping annotation'); + + return; + } + if (segment instanceof Segment) { + console.warn('You cannot annotate the main segment in a Lambda execution environment'); + + return; + } + segment.addAnnotation(key, value); + } + + public putMetadata(key: string, value: unknown, namespace?: string): void { + const segment = this.getSegment(); + if (segment === undefined) { + console.warn('No active segment or subsegment found, skipping metadata addition'); + + return; + } + if (segment instanceof Segment) { + console.warn('You cannot add metadata to the main segment in a Lambda execution environment'); + + return; + } + segment.addMetadata(key, value, namespace); + } + public setContextMissingStrategy(strategy: unknown): void { setContextMissingStrategy(strategy as ContextMissingStrategy); } diff --git a/packages/tracer/src/provider/ProviderServiceInterface.ts b/packages/tracer/src/provider/ProviderServiceInterface.ts index 4b4c1bef96..2376255767 100644 --- a/packages/tracer/src/provider/ProviderServiceInterface.ts +++ b/packages/tracer/src/provider/ProviderServiceInterface.ts @@ -25,6 +25,10 @@ interface ProviderServiceInterface { captureFunc(name: string, fcn: (subsegment?: Subsegment) => unknown, parent?: Segment | Subsegment): unknown captureHTTPsGlobal(): void + + putAnnotation(key: string, value: string | number | boolean): void + + putMetadata(key: string, value: unknown, namespace?: string): void } export { diff --git a/packages/tracer/tests/unit/ProviderService.test.ts b/packages/tracer/tests/unit/ProviderService.test.ts index fdafaf43a3..7335b70fd2 100644 --- a/packages/tracer/tests/unit/ProviderService.test.ts +++ b/packages/tracer/tests/unit/ProviderService.test.ts @@ -1,15 +1,31 @@ /** * Test ProviderService class * - * @group unit/tracer/all + * @group unit/tracer/providerservice */ import { ProviderService } from '../../src/provider'; -import { captureAWS, captureAWSClient, captureAWSv3Client, captureAsyncFunc, captureHTTPsGlobal, captureFunc, getNamespace, getSegment, setContextMissingStrategy, setDaemonAddress, setLogger, setSegment, Subsegment } from 'aws-xray-sdk-core'; +import { + captureAWS, + captureAWSClient, + captureAWSv3Client, + captureAsyncFunc, + captureHTTPsGlobal, + captureFunc, + getNamespace, + getSegment, + setContextMissingStrategy, + setDaemonAddress, + setLogger, + setSegment, + Subsegment, + Segment +} from 'aws-xray-sdk-core'; import http from 'http'; import https from 'https'; jest.mock('aws-xray-sdk-core', () => ({ + ...jest.requireActual('aws-xray-sdk-core'), captureAWS: jest.fn(), captureAWSClient: jest.fn(), captureAWSv3Client: jest.fn(), @@ -21,7 +37,7 @@ jest.mock('aws-xray-sdk-core', () => ({ setContextMissingStrategy: jest.fn(), setDaemonAddress: jest.fn(), setLogger: jest.fn(), - setSegment: jest.fn() + setSegment: jest.fn(), })); describe('Class: ProviderService', () => { @@ -265,4 +281,124 @@ describe('Class: ProviderService', () => { }); + describe('Method: putAnnotation', () => { + + test('when called and there is no segment, it logs a warning and does not throw', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const logSpy = jest.spyOn(console, 'warn').mockImplementation(); + + // Act + provider.putAnnotation('foo', 'bar'); + + // Assess + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith('No active segment or subsegment found, skipping annotation'); + + }); + + test('when called and the current segment is not a subsegment, it logs a warning and does not annotate the segment', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const facade = new Segment('facade'); + const logWarningSpy = jest.spyOn(console, 'warn').mockImplementation(); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => new Segment('facade')); + const addAnnotationSpy = jest.spyOn(facade, 'addAnnotation'); + + // Act + provider.putAnnotation('foo', 'bar'); + + // Assess + expect(logWarningSpy).toHaveBeenCalledTimes(1); + expect(logWarningSpy).toHaveBeenCalledWith( + 'You cannot annotate the main segment in a Lambda execution environment' + ); + expect(addAnnotationSpy).toHaveBeenCalledTimes(0); + + }); + + test('when called and the current segment is a subsegment, it annotates it', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const segment = new Subsegment('## dummySegment'); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => segment); + const segmentSpy = jest.spyOn(segment, 'addAnnotation'); + + // Act + provider.putAnnotation('foo', 'bar'); + + // Assess + expect(segmentSpy).toHaveBeenCalledTimes(1); + expect(segmentSpy).toHaveBeenCalledWith('foo', 'bar'); + + }); + + }); + + describe('Method: putMetadata', () => { + + test('when called and there is no segment, it logs a warning and does not throw', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const logWarningSpy = jest.spyOn(console, 'warn').mockImplementation(); + + // Act + provider.putMetadata('foo', 'bar'); + + // Assess + expect(logWarningSpy).toHaveBeenCalledTimes(1); + expect(logWarningSpy).toHaveBeenCalledWith( + 'No active segment or subsegment found, skipping metadata addition' + ); + + }); + + test('when called and the current segment is not a subsegment, it logs a warning and does not annotate the segment', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const facade = new Segment('facade'); + const logSpy = jest.spyOn(console, 'warn').mockImplementation(); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => new Segment('facade')); + const facadeSpy = jest.spyOn(facade, 'addMetadata'); + + // Act + provider.putMetadata('foo', 'bar'); + + // Assess + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith( + 'You cannot add metadata to the main segment in a Lambda execution environment' + ); + expect(facadeSpy).toHaveBeenCalledTimes(0); + + }); + + test('when called and the current segment is a subsegment, it adds the metadata', () => { + + // Prepare + const provider: ProviderService = new ProviderService(); + const segment = new Subsegment('## dummySegment'); + jest.spyOn(provider, 'getSegment') + .mockImplementation(() => segment); + const segmentSpy = jest.spyOn(segment, 'addMetadata'); + + // Act + provider.putMetadata('foo', 'bar', 'baz'); + + // Assess + expect(segmentSpy).toHaveBeenCalledTimes(1); + expect(segmentSpy).toHaveBeenCalledWith('foo', 'bar', 'baz'); + + }); + + }); + }); \ 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 d46db1164b..dd9383861f 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -250,6 +250,17 @@ describe('Class: Tracer', () => { }); + test('when called and the segment is not found, it returns instead of throwing', () => { + + // Prepare + const tracer: Tracer = new Tracer(); + jest.spyOn(tracer, 'getSegment').mockImplementation(() => undefined); + + // Act & Assess + expect(() => tracer.addErrorAsMetadata(new Error('foo'))).not.toThrow(); + + }); + }); describe('Method: getRootXrayTraceId', () => { @@ -271,28 +282,20 @@ describe('Class: Tracer', () => { describe('Method: getSegment', () => { - test('when called outside of a namespace or without parent segment, and tracing is enabled, it throws an error', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - - // Act / Assess - expect(() => { - tracer.getSegment(); - }).toThrow('Failed to get the current sub/segment from the context.'); - - }); - - test('when called and no segment is returned, while tracing is enabled, it throws an error', () => { + test('when called and no segment is returned, it logs a warning', () => { // Prepare const tracer: Tracer = new Tracer(); jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => undefined); + jest.spyOn(console, 'warn').mockImplementation(() => null); - // Act / Assess - expect(() => { - tracer.getSegment(); - }).toThrow('Failed to get the current sub/segment from the context.'); + // Act + tracer.getSegment(); + + // Assess + expect(console.warn).toHaveBeenCalledWith( + 'Failed to get the current sub/segment from the context, this is likely because you are not using the Tracer in a Lambda function.' + ); }); @@ -307,8 +310,8 @@ describe('Class: Tracer', () => { // Assess expect(segment).toBeInstanceOf(Subsegment); - expect(segment.name).toBe('## Dummy segment'); - + expect((segment as Subsegment).name).toBe('## Dummy segment'); + }); test('when called within a namespace, it returns the parent segment', () => { @@ -388,71 +391,28 @@ describe('Class: Tracer', () => { // Prepare const tracer: Tracer = new Tracer({ enabled: false }); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addAnnotationSpy = jest.spyOn(facadeSegment, 'addAnnotation'); + const putAnnotationSpy = jest.spyOn(tracer.provider, 'putAnnotation'); // Act tracer.putAnnotation('foo', 'bar'); // Assess - expect('annotations' in facadeSegment).toBe(false); - expect(addAnnotationSpy).toBeCalledTimes(0); + expect(putAnnotationSpy).toBeCalledTimes(0); }); - test('when called outside of a namespace or without parent segment, it throws an error', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - - // Act / Assess - expect(() => { - tracer.putAnnotation('foo', 'bar'); - }).toThrow('Failed to get the current sub/segment from the context.'); - - }); - - test('when called within a namespace and on the main segment, it does nothing', () => { + test('it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addAnnotationSpy = jest.spyOn(facadeSegment, 'addAnnotation'); + const putAnnotationSpy = jest.spyOn(tracer.provider, 'putAnnotation'); // Act tracer.putAnnotation('foo', 'bar'); // Assess - expect('annotations' in facadeSegment).toBe(false); - expect(addAnnotationSpy).toBeCalledTimes(0); - expect(console.warn).toBeCalledTimes(1); - expect(console.warn).toHaveBeenNthCalledWith(1, 'You cannot annotate the main segment in a Lambda execution environment'); - - }); - - test('when called within a namespace and on a subsegment, it adds an annotation', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addAnnotationSpy = jest.spyOn(newSubsegment, 'addAnnotation'); - - // Act - tracer.putAnnotation('foo', 'bar'); - - // Assess - expect('annotations' in newSubsegment).toBe(true); - expect(addAnnotationSpy).toBeCalledTimes(1); - expect(addAnnotationSpy).toBeCalledWith('foo', 'bar'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'annotations': { - foo: 'bar' - } - })); + expect(putAnnotationSpy).toBeCalledTimes(1); + expect(putAnnotationSpy).toBeCalledWith('foo', 'bar'); }); @@ -460,128 +420,63 @@ describe('Class: Tracer', () => { describe('Method: putMetadata', () => { - test('when called while tracing is disabled, it does nothing', () => { + test('when tracing is disabled, it does nothing', () => { // Prepare const tracer: Tracer = new Tracer({ enabled: false }); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addMetadataSpy = jest.spyOn(facadeSegment, 'addMetadata'); + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); // Act tracer.putMetadata('foo', 'bar'); // Assess - expect('metadata' in facadeSegment).toBe(false); - expect(addMetadataSpy).toBeCalledTimes(0); - - }); - - test('when called outside of a namespace or without parent segment, it throws an error', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - - // Act / Assess - expect(() => { - tracer.putMetadata('foo', 'bar'); - }).toThrow('Failed to get the current sub/segment from the context.'); + expect(putMetadataSpy).toBeCalledTimes(0); }); - test('when called within a namespace and on the main segment, it does nothing', () => { + test('it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); - jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => facadeSegment); - const addMetadataSpy = jest.spyOn(facadeSegment, 'addMetadata'); - - // Act - tracer.putMetadata('foo', 'bar'); - - // Assess - expect('metadata' in facadeSegment).toBe(false); - expect(addMetadataSpy).toBeCalledTimes(0); - expect(console.warn).toBeCalledTimes(1); - expect(console.warn).toHaveBeenNthCalledWith(1, 'You cannot add metadata to the main segment in a Lambda execution environment'); - - }); - - test('when called within a namespace and on a subsegment, it adds the metadata with the default service name as namespace', () => { - - // Prepare - const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addMetadataSpy = jest.spyOn(newSubsegment, 'addMetadata'); - + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); + // Act tracer.putMetadata('foo', 'bar'); // Assess - expect('metadata' in newSubsegment).toBe(true); - expect(addMetadataSpy).toBeCalledTimes(1); - expect(addMetadataSpy).toBeCalledWith('foo', 'bar', 'hello-world'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'metadata': { - 'hello-world': { - foo: 'bar' - } - } - })); + expect(putMetadataSpy).toBeCalledTimes(1); + // The default namespace is 'hello-world' and it comes from the service name environment variable + expect(putMetadataSpy).toBeCalledWith('foo', 'bar', 'hello-world'); + }); - test('when called within a namespace and on a subsegment, and with a custom namespace as an argument, it adds the metadata correctly', () => { + test('when passed a custom namespace, it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addMetadataSpy = jest.spyOn(newSubsegment, 'addMetadata'); - + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); + // Act tracer.putMetadata('foo', 'bar', 'baz'); // Assess - expect('metadata' in newSubsegment).toBe(true); - expect(addMetadataSpy).toBeCalledTimes(1); - expect(addMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'metadata': { - 'baz': { - foo: 'bar' - } - } - })); + expect(putMetadataSpy).toBeCalledTimes(1); + expect(putMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); }); - test('when called within a namespace and on a subsegment, and while a custom namespace was set in the class, it adds the metadata correctly', () => { + test('when a custom namespace was set in the constructor, it calls the provider method with the correct arguments', () => { // Prepare const tracer: Tracer = new Tracer({ serviceName: 'baz' }); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementation(() => newSubsegment); - const addMetadataSpy = jest.spyOn(newSubsegment, 'addMetadata'); + const putMetadataSpy = jest.spyOn(tracer.provider, 'putMetadata'); // Act tracer.putMetadata('foo', 'bar'); // Assess - expect('metadata' in newSubsegment).toBe(true); - expect(addMetadataSpy).toBeCalledTimes(1); - expect(addMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); - expect(newSubsegment).toEqual(expect.objectContaining({ - 'metadata': { - 'baz': { - foo: 'bar' - } - } - })); + expect(putMetadataSpy).toBeCalledTimes(1); + expect(putMetadataSpy).toBeCalledWith('foo', 'bar', 'baz'); }); @@ -621,10 +516,9 @@ describe('Class: Tracer', () => { // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = 'false'; 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 putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -643,7 +537,7 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect('metadata' in newSubsegment).toBe(false); + expect(putMetadataSpy).toHaveBeenCalledTimes(0); delete process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE; }); @@ -652,10 +546,9 @@ describe('Class: Tracer', () => { // 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 addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler({ captureResponse: false }) @@ -674,7 +567,7 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); - expect('metadata' in newSubsegment).toBe(false); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(0); }); @@ -682,11 +575,9 @@ describe('Class: Tracer', () => { // 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 addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler({ captureResponse: true }) @@ -706,28 +597,18 @@ describe('Class: Tracer', () => { // 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', - }, - }, - } - })); - + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addResponseAsMetadataSpy).toHaveBeenCalledWith({ foo: 'bar' }, 'index.handler'); + }); test('when used as decorator and with standard config, 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'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -747,16 +628,8 @@ describe('Class: Tracer', () => { // 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', - }, - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addResponseAsMetadataSpy).toHaveBeenCalledWith({ foo: 'bar' }, 'index.handler'); }); @@ -765,13 +638,13 @@ describe('Class: Tracer', () => { // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_ERROR = 'false'; 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 newSubsegment = new Subsegment('### dummyMethod'); + jest.spyOn(tracer, 'getSegment') + .mockImplementation(() => newSubsegment); const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); + const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -786,31 +659,26 @@ describe('Class: Tracer', () => { // 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); + expect.assertions(4); delete process.env.POWERTOOLS_TRACER_CAPTURE_ERROR; }); - test('when used as decorator and with standard config, it captures the exception correctly', async () => { + test('when used as decorator and with standard config, it captures the exception', async () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => new Segment('facade', process.env._X_AMZN_TRACE_ID || null)) - .mockImplementation(() => newSubsegment); - /* jest.spyOn(tracer.provider, 'captureAsyncFunc').mockImplementation( - () => tracer.provider.captureAsyncFunc('## index.handler', )); */ - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); + const addErrorAsMetadataSpy = jest.spyOn(tracer, 'addErrorAsMetadata'); + const newSubsegment = new Subsegment('### dummyMethod'); + jest.spyOn(tracer, 'getSegment') + .mockImplementation(() => newSubsegment); + const addErrorFlagSpy = jest.spyOn(newSubsegment, 'addErrorFlag'); const addErrorSpy = jest.spyOn(newSubsegment, 'addError'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -825,28 +693,21 @@ describe('Class: Tracer', () => { // 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(true); + expect(addErrorAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addErrorAsMetadataSpy).toHaveBeenCalledWith(expect.any(Error)); + expect(addErrorFlagSpy).toHaveBeenCalledTimes(0); expect(addErrorSpy).toHaveBeenCalledTimes(1); - expect(addErrorSpy).toHaveBeenCalledWith(new Error('Exception thrown!'), false); expect.assertions(6); }); - test('when used as decorator and with standard config, it annotates ColdStart correctly', async () => { + test('when used as decorator and with standard config, it annotates ColdStart', async () => { // Prepare const tracer: Tracer = new Tracer(); - const newSubsegmentFirstInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const newSubsegmentSecondInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => newSubsegmentFirstInvocation) - .mockImplementation(() => newSubsegmentSecondInvocation); - setContextMissingStrategy(() => null); const captureAsyncFuncSpy = createCaptureAsyncFuncMock(tracer.provider); - const putAnnotationSpy = jest.spyOn(tracer, 'putAnnotation'); + const annotateColdStartSpy = jest.spyOn(tracer, 'annotateColdStart'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -862,41 +723,21 @@ describe('Class: Tracer', () => { // Act await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); - await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); // Assess - expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(2); + expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); - expect(putAnnotationSpy.mock.calls.filter(call => - call[0] === 'ColdStart' - )).toEqual([ - [ 'ColdStart', true ], - [ 'ColdStart', false ], - ]); - expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': true, - }) - })); - expect(newSubsegmentSecondInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': false, - }) - })); + expect(annotateColdStartSpy).toHaveBeenCalledTimes(1); }); - test('when used as decorator and with standard config, it annotates Service correctly', async () => { + test('when used as decorator and with standard config, it adds the Service annotation', 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 = createCaptureAsyncFuncMock(tracer.provider); + const addServiceNameAnnotationSpy = jest.spyOn(tracer, 'addServiceNameAnnotation'); + class Lambda implements LambdaInterface { @tracer.captureLambdaHandler() @@ -916,12 +757,8 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'Service': 'hello-world', - }) - })); + // The first call is for the Cold Start annotation + expect(addServiceNameAnnotationSpy).toHaveBeenCalledTimes(1); }); @@ -1045,12 +882,9 @@ describe('Class: Tracer', () => { // 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'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureMethod() @@ -1074,14 +908,8 @@ describe('Class: Tracer', () => { // 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', - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); + expect(addResponseAsMetadataSpy).toHaveBeenCalledWith('foo bar', 'dummyMethod'); }); @@ -1089,12 +917,9 @@ describe('Class: Tracer', () => { // 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'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureMethod({ captureResponse: false }) @@ -1118,16 +943,7 @@ describe('Class: Tracer', () => { // 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', - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(0); }); @@ -1135,12 +951,9 @@ describe('Class: Tracer', () => { // 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'); + const addResponseAsMetadataSpy = jest.spyOn(tracer, 'addResponseAsMetadata'); + class Lambda implements LambdaInterface { @tracer.captureMethod() @@ -1164,14 +977,7 @@ describe('Class: Tracer', () => { // 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', - }, - } - })); + expect(addResponseAsMetadataSpy).toHaveBeenCalledTimes(1); }); @@ -1340,11 +1146,7 @@ describe('Class: Tracer', () => { // 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); + const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc'); // Creating custom external decorator // eslint-disable-next-line func-style @@ -1386,14 +1188,7 @@ describe('Class: Tracer', () => { 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', - }, - } - })); + expect(captureAsyncFuncSpy).toHaveBeenCalledWith('### dummyMethod', expect.any(Function)); }); diff --git a/packages/tracer/tests/unit/middy.test.ts b/packages/tracer/tests/unit/middy.test.ts index f1b33b01be..6b8a2ca7b3 100644 --- a/packages/tracer/tests/unit/middy.test.ts +++ b/packages/tracer/tests/unit/middy.test.ts @@ -90,21 +90,18 @@ describe('Middy middleware', () => { // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = 'false'; 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) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect('metadata' in newSubsegment).toBe(false); + expect(putMetadataSpy).toHaveBeenCalledTimes(0); delete process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE; }); @@ -113,21 +110,18 @@ describe('Middy middleware', () => { // 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) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer, { captureResponse: false })); + })).use(captureLambdaHandler(tracer, { captureResponse: false })); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect('metadata' in newSubsegment).toBe(false); + expect(putMetadataSpy).toHaveBeenCalledTimes(0); }); @@ -135,30 +129,19 @@ describe('Middy middleware', () => { // 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) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer, { captureResponse: true })); + })).use(captureLambdaHandler(tracer, { captureResponse: true })); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - metadata: { - 'hello-world': { - 'index.handler response': { - foo: 'bar', - }, - }, - } - })); + expect(putMetadataSpy).toHaveBeenCalledTimes(1); + expect(putMetadataSpy).toHaveBeenCalledWith('index.handler response', { foo: 'bar' }); }); @@ -166,30 +149,19 @@ describe('Middy middleware', () => { // 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) => ({ + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + const putMetadataSpy = jest.spyOn(tracer, 'putMetadata'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - metadata: { - 'hello-world': { - 'index.handler response': { - foo: 'bar', - }, - }, - } - })); + expect(putMetadataSpy).toHaveBeenCalledTimes(1); + expect(putMetadataSpy).toHaveBeenCalledWith('index.handler response', { foo: 'bar' }); }); @@ -251,46 +223,27 @@ describe('Middy middleware', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade'); - const newSubsegmentFirstInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const newSubsegmentSecondInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(() => ({})); jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => facadeSegment) - .mockImplementationOnce(() => newSubsegmentFirstInvocation) - .mockImplementationOnce(() => facadeSegment) - .mockImplementation(() => newSubsegmentSecondInvocation); - setContextMissingStrategy(() => null); + .mockImplementationOnce(() => new Segment('facade')) + .mockImplementationOnce(() => new Subsegment('## index.handler')) + .mockImplementationOnce(() => new Segment('facade')) + .mockImplementation(() => new Subsegment('## index.handler')); const putAnnotationSpy = jest.spyOn(tracer, 'putAnnotation'); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(4); - expect(putAnnotationSpy.mock.calls.filter(call => - call[0] === 'ColdStart' - )).toEqual([ - [ 'ColdStart', true ], - [ 'ColdStart', false ], - ]); - expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': true, - }) - })); - expect(newSubsegmentSecondInvocation).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'ColdStart': false, - }) - })); + // 2x Cold Start + 2x Service + expect(putAnnotationSpy).toHaveBeenCalledTimes(4); + expect(putAnnotationSpy).toHaveBeenNthCalledWith(1, 'ColdStart', true); + expect(putAnnotationSpy).toHaveBeenNthCalledWith(3, 'ColdStart', false); }); @@ -298,35 +251,23 @@ describe('Middy middleware', () => { // Prepare const tracer: Tracer = new Tracer(); - const facadeSegment = new Segment('facade'); - const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); - const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); + jest.spyOn(tracer.provider, 'setSegment').mockImplementation(() => ({})); jest.spyOn(tracer.provider, 'getSegment') - .mockImplementationOnce(() => facadeSegment) - .mockImplementation(() => newSubsegment); - setContextMissingStrategy(() => null); - const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({ + .mockImplementationOnce(() => new Segment('facade')) + .mockImplementation(() => new Subsegment('## index.handler')); + const putAnnotationSpy = jest.spyOn(tracer, 'putAnnotation'); + + const handler = middy(async (_event: unknown, _context: Context) => ({ foo: 'bar' - }); - const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); + })).use(captureLambdaHandler(tracer)); // Act - await handler({}, context, () => console.log('Lambda invoked!')); + await handler({}, context); // Assess - expect(setSegmentSpy).toHaveBeenCalledTimes(2); - expect(setSegmentSpy.mock.calls.map(arg => ({ - name: arg[0].name, - }))).toEqual([ - expect.objectContaining({ name: '## index.handler' }), - expect.objectContaining({ name: 'facade' }), - ]); - expect(newSubsegment).toEqual(expect.objectContaining({ - name: '## index.handler', - annotations: expect.objectContaining({ - 'Service': 'hello-world', - }) - })); + // The first call is for the Cold Start annotation + expect(putAnnotationSpy).toHaveBeenCalledTimes(2); + expect(putAnnotationSpy).toHaveBeenNthCalledWith(2, 'Service', 'hello-world'); });