From d7c9b83461bd9c54853cbbb91ece8311efa3f584 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 14 Apr 2022 10:11:48 +0000 Subject: [PATCH 1/4] feat: allow to reuse Tracer --- packages/tracing/src/Tracer.ts | 62 +++---- packages/tracing/tests/unit/Tracer.test.ts | 176 ++++++++++---------- packages/tracing/tests/unit/helpers.test.ts | 27 ++- packages/tracing/tests/unit/middy.test.ts | 31 ++-- 4 files changed, 163 insertions(+), 133 deletions(-) diff --git a/packages/tracing/src/Tracer.ts b/packages/tracing/src/Tracer.ts index b25d940777..115681a586 100644 --- a/packages/tracing/src/Tracer.ts +++ b/packages/tracing/src/Tracer.ts @@ -113,27 +113,33 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core'; */ class Tracer extends Utility implements TracerInterface { - public provider: ProviderServiceInterface; - + public provider: ProviderServiceInterface = new ProviderService(); + + private static _instance?: Tracer; + private captureError: boolean = true; private captureHTTPsRequests: boolean = true; - + private captureResponse: boolean = true; private customConfigService?: ConfigServiceInterface; - + private envVarsService?: EnvironmentVariablesService; - + private serviceName?: string; - + private tracingEnabled: boolean = true; public constructor(options: TracerOptions = {}) { super(); + if (Tracer._instance) { + return Tracer._instance; + } + Tracer._instance = this; + this.setOptions(options); - this.provider = new ProviderService(); if (this.isTracingEnabled() && this.captureHTTPsRequests) { this.provider.captureHTTPsGlobal(); } @@ -269,7 +275,7 @@ class Tracer extends Utility implements TracerInterface { // instrumentation contract like most base clients. // For detailed explanation see: https://github.com/awslabs/aws-lambda-powertools-typescript/issues/524#issuecomment-1024493662 this.provider.captureAWSClient((service as T & { service: T }).service); - + return service; } catch { throw error; @@ -344,7 +350,7 @@ class Tracer extends Utility implements TracerInterface { descriptor.value = ((event, context, callback) => { if (!this.isTracingEnabled()) { - return originalMethod?.apply(target, [ event, context, callback ]); + return originalMethod?.apply(target, [event, context, callback]); } return this.provider.captureAsyncFunc(`## ${process.env._HANDLER}`, async subsegment => { @@ -352,7 +358,7 @@ class Tracer extends Utility implements TracerInterface { this.addServiceNameAnnotation(); let result: unknown; try { - result = await originalMethod?.apply(target, [ event, context, callback ]); + result = await originalMethod?.apply(target, [event, context, callback]); this.addResponseAsMetadata(result, process.env._HANDLER); } catch (error) { this.addErrorAsMetadata(error as Error); @@ -361,7 +367,7 @@ class Tracer extends Utility implements TracerInterface { subsegment?.close(); subsegment?.flush(); } - + return result; }); }) as Handler; @@ -408,7 +414,7 @@ class Tracer extends Utility implements TracerInterface { public captureMethod(): MethodDecorator { return (target, _propertyKey, descriptor) => { const originalMethod = descriptor.value; - + descriptor.value = (...args: unknown[]) => { if (!this.isTracingEnabled()) { return originalMethod?.apply(target, [...args]); @@ -426,7 +432,7 @@ class Tracer extends Utility implements TracerInterface { } finally { subsegment?.close(); } - + return result; }); }; @@ -434,7 +440,7 @@ class Tracer extends Utility implements TracerInterface { return descriptor; }; } - + /** * Get the active segment or subsegment in the current scope. * @@ -461,11 +467,11 @@ class Tracer extends Utility implements TracerInterface { if (!this.isTracingEnabled()) { return new Subsegment('## Dummy segment'); } - const segment = this.provider.getSegment(); + const segment = this.provider.getSegment(); if (segment === undefined) { throw new Error('Failed to get the current sub/segment from the context.'); } - + return segment; } @@ -506,12 +512,12 @@ class Tracer extends Utility implements TracerInterface { 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); } - + /** * Adds metadata to existing segment or subsegment. * @@ -539,14 +545,14 @@ class Tracer extends Utility implements TracerInterface { 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); } - + /** * Sets the passed subsegment as the current active subsegment. * @@ -571,7 +577,7 @@ class Tracer extends Utility implements TracerInterface { */ public setSegment(segment: Segment | Subsegment): void { if (!this.isTracingEnabled()) return; - + return this.provider.setSegment(segment); } @@ -588,9 +594,9 @@ class Tracer extends Utility implements TracerInterface { * Used internally during initialization. */ private getEnvVarsService(): EnvironmentVariablesService { - return this.envVarsService; + return this.envVarsService; } - + /** * Determine if we are running in a Lambda execution environment. * Used internally during initialization. @@ -598,7 +604,7 @@ class Tracer extends Utility implements TracerInterface { private isLambdaExecutionEnv(): boolean { return this.getEnvVarsService()?.getAwsExecutionEnv() !== ''; } - + /** * Determine if we are running inside a SAM CLI process. * Used internally during initialization. @@ -770,21 +776,21 @@ class Tracer extends Utility implements TracerInterface { private setTracingEnabled(enabled?: boolean): void { if (enabled !== undefined && !enabled) { this.tracingEnabled = enabled; - + return; } const customConfigValue = this.getCustomConfigService()?.getTracingEnabled(); if (customConfigValue !== undefined && customConfigValue.toLowerCase() === 'false') { this.tracingEnabled = false; - + return; } const envVarsValue = this.getEnvVarsService()?.getTracingEnabled(); if (envVarsValue.toLowerCase() === 'false') { this.tracingEnabled = false; - + return; } diff --git a/packages/tracing/tests/unit/Tracer.test.ts b/packages/tracing/tests/unit/Tracer.test.ts index 377ff13136..222216c517 100644 --- a/packages/tracing/tests/unit/Tracer.test.ts +++ b/packages/tracing/tests/unit/Tracer.test.ts @@ -45,6 +45,10 @@ describe('Class: Tracer', () => { process.env = { ...ENVIRONMENT_VARIABLES }; }); + afterEach(() => { + Tracer['_instance'] = undefined; + }); + afterAll(() => { process.env = ENVIRONMENT_VARIABLES; }); @@ -80,10 +84,10 @@ describe('Class: Tracer', () => { // Assess expect(putAnnotationSpy).toBeCalledTimes(4); expect(putAnnotationSpy.mock.calls).toEqual([ - [ 'ColdStart', true ], - [ 'ColdStart', false ], - [ 'ColdStart', false ], - [ 'ColdStart', false ], + ['ColdStart', true], + ['ColdStart', false], + ['ColdStart', false], + ['ColdStart', false], ]); }); @@ -264,7 +268,7 @@ describe('Class: Tracer', () => { // Prepare const tracer: Tracer = new Tracer(); - + // Act / Assess expect(() => { tracer.getSegment(); @@ -277,7 +281,7 @@ describe('Class: Tracer', () => { // Prepare const tracer: Tracer = new Tracer(); jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => undefined); - + // Act / Assess expect(() => { tracer.getSegment(); @@ -305,10 +309,10 @@ describe('Class: Tracer', () => { // Prepare const tracer: Tracer = new Tracer(); jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => new Segment('facade', process.env._X_AMZN_TRACE_ID || null)); - + // Act const segment = tracer.getSegment(); - + // Assess expect(segment).toBeInstanceOf(Segment); expect(segment).toEqual(expect.objectContaining({ @@ -322,10 +326,10 @@ describe('Class: Tracer', () => { describe('Method: setSegment', () => { test('when called outside of a namespace or without parent segment, and Tracer is enabled, it throws an error', () => { - + // Prepare const tracer: Tracer = new Tracer(); - + // Act / Assess expect(() => { const newSubsegment = new Subsegment('## foo.bar'); @@ -334,28 +338,28 @@ describe('Class: Tracer', () => { }); test('when called outside of a namespace or without parent segment, and tracing is disabled, it does nothing', () => { - + // Prepare delete process.env.AWS_EXECUTION_ENV; // This will disable the tracer, simulating local execution const tracer: Tracer = new Tracer(); const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment'); - + // Act const newSubsegment = new Subsegment('## foo.bar'); tracer.setSegment(newSubsegment); - + // Assess expect(setSegmentSpy).toBeCalledTimes(0); }); test('when called within a namespace, it sets the segment', () => { - + // Prepare const tracer: Tracer = new Tracer(); jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => new Segment('facade', process.env._X_AMZN_TRACE_ID || null)); const providerSetSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(() => ({})); - + // Act const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); tracer.setSegment(newSubsegment); @@ -372,7 +376,7 @@ describe('Class: Tracer', () => { }); describe('Method: putAnnotation', () => { - + test('when called while tracing is disabled, it does nothing', () => { // Prepare @@ -383,13 +387,13 @@ describe('Class: Tracer', () => { // Act tracer.putAnnotation('foo', 'bar'); - + // Assess expect('annotations' in facadeSegment).toBe(false); expect(addAnnotationSpy).toBeCalledTimes(0); }); - + test('when called outside of a namespace or without parent segment, it throws an error', () => { // Prepare @@ -403,7 +407,7 @@ describe('Class: Tracer', () => { }); test('when called within a namespace and on the main segment, it does nothing', () => { - + // Prepare const tracer: Tracer = new Tracer(); const facadeSegment = new Segment('facade', process.env._X_AMZN_TRACE_ID || null); @@ -412,7 +416,7 @@ describe('Class: Tracer', () => { // Act tracer.putAnnotation('foo', 'bar'); - + // Assess expect('annotations' in facadeSegment).toBe(false); expect(addAnnotationSpy).toBeCalledTimes(0); @@ -422,7 +426,7 @@ describe('Class: Tracer', () => { }); 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'); @@ -432,7 +436,7 @@ describe('Class: Tracer', () => { // Act tracer.putAnnotation('foo', 'bar'); - + // Assess expect('annotations' in newSubsegment).toBe(true); expect(addAnnotationSpy).toBeCalledTimes(1); @@ -448,9 +452,9 @@ describe('Class: Tracer', () => { }); describe('Method: putMetadata', () => { - + test('when called while 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); @@ -459,7 +463,7 @@ describe('Class: Tracer', () => { // Act tracer.putMetadata('foo', 'bar'); - + // Assess expect('metadata' in facadeSegment).toBe(false); expect(addMetadataSpy).toBeCalledTimes(0); @@ -467,48 +471,48 @@ describe('Class: Tracer', () => { }); 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.'); }); - + test('when called within a namespace and on the main segment, it does nothing', () => { - + // 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'); - + // Act tracer.putMetadata('foo', 'bar'); - + // Assess expect('metadata' in newSubsegment).toBe(true); expect(addMetadataSpy).toBeCalledTimes(1); @@ -521,19 +525,19 @@ describe('Class: Tracer', () => { } })); }); - + test('when called within a namespace and on a subsegment, and with a custom namespace as an argument, it adds the metadata correctly', () => { - + // 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'); - + // Act tracer.putMetadata('foo', 'bar', 'baz'); - + // Assess expect('metadata' in newSubsegment).toBe(true); expect(addMetadataSpy).toBeCalledTimes(1); @@ -547,9 +551,9 @@ describe('Class: Tracer', () => { })); }); - + 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', () => { - + // Prepare const tracer: Tracer = new Tracer({ serviceName: 'baz' }); const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo.bar'); @@ -559,7 +563,7 @@ describe('Class: Tracer', () => { // Act tracer.putMetadata('foo', 'bar'); - + // Assess expect('metadata' in newSubsegment).toBe(true); expect(addMetadataSpy).toBeCalledTimes(1); @@ -571,15 +575,15 @@ describe('Class: Tracer', () => { } } })); - + }); }); describe('Method: captureLambdaHandler', () => { - + test('when used as decorator while tracing is disabled, it does nothing', async () => { - + // Prepare const tracer: Tracer = new Tracer({ enabled: false }); jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => new Segment('facade', process.env._X_AMZN_TRACE_ID || null)); @@ -594,9 +598,9 @@ describe('Class: Tracer', () => { foo: 'bar' } as unknown as TResult)); } - + } - + // Act await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); @@ -624,9 +628,9 @@ describe('Class: Tracer', () => { foo: 'bar' } as unknown as TResult)); } - + } - + // Act await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); @@ -634,11 +638,11 @@ describe('Class: Tracer', () => { expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect('metadata' in newSubsegment).toBe(false); delete process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE; - + }); 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'); @@ -656,9 +660,9 @@ describe('Class: Tracer', () => { foo: 'bar' } as unknown as TResult)); } - + } - + // Act await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); @@ -698,9 +702,9 @@ describe('Class: Tracer', () => { 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); @@ -717,7 +721,7 @@ describe('Class: Tracer', () => { }); test('when used as decorator and with standard config, it captures the exception correctly', async () => { - + // Prepare const tracer: Tracer = new Tracer(); const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); @@ -737,9 +741,9 @@ describe('Class: Tracer', () => { 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); @@ -750,11 +754,11 @@ describe('Class: Tracer', () => { 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 () => { - + // Prepare const tracer: Tracer = new Tracer(); const newSubsegmentFirstInvocation: Segment | Subsegment | undefined = new Subsegment('## index.handler'); @@ -775,9 +779,9 @@ describe('Class: Tracer', () => { foo: 'bar' } as unknown as TResult)); } - + } - + // Act await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); @@ -785,11 +789,11 @@ describe('Class: Tracer', () => { // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(2); expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## index.handler', expect.anything()); - expect(putAnnotationSpy.mock.calls.filter(call => + expect(putAnnotationSpy.mock.calls.filter(call => call[0] === 'ColdStart' )).toEqual([ - [ 'ColdStart', true ], - [ 'ColdStart', false ], + ['ColdStart', true], + ['ColdStart', false], ]); expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ name: '## index.handler', @@ -807,7 +811,7 @@ describe('Class: Tracer', () => { }); test('when used as decorator and with standard config, it annotates Service correctly', async () => { - + // Prepare const tracer: Tracer = new Tracer(); const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); @@ -825,9 +829,9 @@ describe('Class: Tracer', () => { foo: 'bar' } as unknown as TResult)); } - + } - + // Act await new Lambda().handler(event, context, () => console.log('Lambda invoked!')); @@ -865,7 +869,7 @@ describe('Class: Tracer', () => { 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)); } @@ -899,7 +903,7 @@ describe('Class: Tracer', () => { 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)); } @@ -943,7 +947,7 @@ describe('Class: Tracer', () => { 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)); } @@ -966,7 +970,7 @@ describe('Class: Tracer', () => { }); describe('Method: captureAWS', () => { - + test('when called while tracing is disabled, it does nothing', () => { // Prepare @@ -1001,7 +1005,7 @@ describe('Class: Tracer', () => { }); describe('Method: captureAWSv3Client', () => { - + test('when called while tracing is disabled, it does nothing', () => { // Prepare const tracer: Tracer = new Tracer({ enabled: false }); @@ -1013,11 +1017,11 @@ describe('Class: Tracer', () => { // Assess expect(captureAWSv3ClientSpy).toBeCalledTimes(0); - + }); test('when called it returns the decorated object that was passed to it', () => { - + // Prepare const tracer: Tracer = new Tracer(); const captureAWSv3ClientSpy = jest.spyOn(tracer.provider, 'captureAWSv3Client') @@ -1029,15 +1033,15 @@ describe('Class: Tracer', () => { // Assess expect(captureAWSv3ClientSpy).toBeCalledTimes(1); expect(captureAWSv3ClientSpy).toBeCalledWith({}); - + }); }); describe('Method: captureAWSClient', () => { - + test('when called while tracing is disabled, it does nothing', () => { - + // Prepare const tracer: Tracer = new Tracer({ enabled: false }); const captureAWSClientSpy = jest.spyOn(tracer.provider, 'captureAWSClient'); @@ -1048,11 +1052,11 @@ describe('Class: Tracer', () => { // Assess expect(captureAWSClientSpy).toBeCalledTimes(0); expect(client).toBeInstanceOf(DynamoDB); - + }); test('when called with a base AWS SDK v2 client, it returns it back instrumented', () => { - + // Prepare const tracer: Tracer = new Tracer(); const captureAWSClientSpy = jest.spyOn(tracer.provider, 'captureAWSClient'); @@ -1064,11 +1068,11 @@ describe('Class: Tracer', () => { expect(captureAWSClientSpy).toBeCalledTimes(1); expect(captureAWSClientSpy).toBeCalledWith(client); expect(client).toBeInstanceOf(DynamoDB); - + }); test('when called with a complex AWS SDK v2 client, it returns it back instrumented', () => { - + // Prepare const tracer: Tracer = new Tracer(); const captureAWSClientSpy = jest.spyOn(tracer.provider, 'captureAWSClient'); @@ -1081,11 +1085,11 @@ describe('Class: Tracer', () => { expect(captureAWSClientSpy).toHaveBeenNthCalledWith(1, client); expect(captureAWSClientSpy).toHaveBeenNthCalledWith(2, (client as unknown as DynamoDB & { service: DynamoDB }).service); expect(client).toBeInstanceOf(DynamoDB.DocumentClient); - + }); test('when called with an uncompatible object, it throws an error', () => { - + // Prepare const tracer: Tracer = new Tracer(); const captureAWSClientSpy = jest.spyOn(tracer.provider, 'captureAWSClient'); @@ -1098,7 +1102,7 @@ describe('Class: Tracer', () => { expect(captureAWSClientSpy).toHaveBeenNthCalledWith(1, {}); expect(captureAWSClientSpy).toHaveBeenNthCalledWith(2, undefined); expect.assertions(4); - + }); }); diff --git a/packages/tracing/tests/unit/helpers.test.ts b/packages/tracing/tests/unit/helpers.test.ts index 7fdf7823d7..6449ffa128 100644 --- a/packages/tracing/tests/unit/helpers.test.ts +++ b/packages/tracing/tests/unit/helpers.test.ts @@ -16,10 +16,25 @@ describe('Helper: createTracer function', () => { process.env = { ...ENVIRONMENT_VARIABLES }; }); + afterEach(() => { + Tracer['_instance'] = undefined; + }); + afterAll(() => { process.env = ENVIRONMENT_VARIABLES; }); + describe('Singleton', () => { + + test('when called multiple times, it should return the same instance', () => { + const tracer1 = createTracer(); + const tracer2 = createTracer(); + + expect(tracer1).toBe(tracer2); + }); + + }); + describe('TracerOptions parameters', () => { test('when no tracer options are passed, returns a Tracer instance with the correct proprieties', () => { // Prepare @@ -35,7 +50,7 @@ describe('Helper: createTracer function', () => { serviceName: 'hello-world', captureHTTPsRequests: true })); - + }); test('when all tracer options are passed, returns a Tracer instance with the correct properties', () => { @@ -57,7 +72,7 @@ describe('Helper: createTracer function', () => { serviceName: 'my-lambda-service', captureHTTPsRequests: false, })); - + }); test('when a custom serviceName is passed, returns a Tracer instance with the correct properties', () => { @@ -146,10 +161,10 @@ describe('Helper: createTracer function', () => { const tracerOptions: TracerOptions = { customConfigService: configService }; - + // Act const tracer = createTracer(tracerOptions); - + // Assess expect(tracer).toBeInstanceOf(Tracer); expect(tracer).toEqual(expect.objectContaining({ @@ -168,7 +183,7 @@ describe('Helper: createTracer function', () => { enabled: true, captureHTTPsRequests: false }; - + // Act const tracer = createTracer(tracerOptions); @@ -187,7 +202,7 @@ describe('Helper: createTracer function', () => { const tracerOptions = { enabled: true, }; - + // Act const tracer = createTracer(tracerOptions); diff --git a/packages/tracing/tests/unit/middy.test.ts b/packages/tracing/tests/unit/middy.test.ts index 55b72dde7a..1580a119e6 100644 --- a/packages/tracing/tests/unit/middy.test.ts +++ b/packages/tracing/tests/unit/middy.test.ts @@ -37,13 +37,18 @@ describe('Middy middleware', () => { process.env = { ...ENVIRONMENT_VARIABLES }; }); + afterEach(() => { + Tracer['_instance'] = undefined; + }); + afterAll(() => { process.env = ENVIRONMENT_VARIABLES; }); + describe('Middleware: captureLambdaHandler', () => { - + test('when used while tracing is disabled, it does nothing', async () => { - + // Prepare const tracer: Tracer = new Tracer({ enabled: false }); const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); @@ -65,7 +70,7 @@ describe('Middy middleware', () => { }); test('when used while tracing is disabled, even if the handler throws an error, it does nothing', async () => { - + // Prepare const tracer: Tracer = new Tracer({ enabled: false }); const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation(); @@ -86,7 +91,7 @@ describe('Middy middleware', () => { }); test('when used while POWERTOOLS_TRACER_CAPTURE_RESPONSE is set to false, it does not capture the response as metadata', async () => { - + // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = 'false'; const tracer: Tracer = new Tracer(); @@ -110,7 +115,7 @@ describe('Middy middleware', () => { }); test('when used 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'); @@ -141,7 +146,7 @@ describe('Middy middleware', () => { }); test('when used while POWERTOOLS_TRACER_CAPTURE_ERROR is set to false, it does not capture the exceptions', async () => { - + // Prepare process.env.POWERTOOLS_TRACER_CAPTURE_ERROR = 'false'; const tracer: Tracer = new Tracer(); @@ -171,7 +176,7 @@ describe('Middy middleware', () => { }); test('when used with standard config, it captures the exception correctly', async () => { - + // Prepare const tracer: Tracer = new Tracer(); const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## index.handler'); @@ -195,7 +200,7 @@ describe('Middy middleware', () => { }); test('when used with standard config, it annotates ColdStart correctly', async () => { - + // Prepare const tracer: Tracer = new Tracer(); const facadeSegment = new Segment('facade'); @@ -217,14 +222,14 @@ describe('Middy middleware', () => { // Act await handler({}, context, () => console.log('Lambda invoked!')); await handler({}, context, () => console.log('Lambda invoked!')); - + // Assess expect(setSegmentSpy).toHaveBeenCalledTimes(4); - expect(putAnnotationSpy.mock.calls.filter(call => + expect(putAnnotationSpy.mock.calls.filter(call => call[0] === 'ColdStart' )).toEqual([ - [ 'ColdStart', true ], - [ 'ColdStart', false ], + ['ColdStart', true], + ['ColdStart', false], ]); expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ name: '## index.handler', @@ -242,7 +247,7 @@ describe('Middy middleware', () => { }); test('when used with standard config, it annotates Service correctly', async () => { - + // Prepare const tracer: Tracer = new Tracer(); const facadeSegment = new Segment('facade'); From 183d1ecb87d914d77d4db58f024c544a03046f55 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 14 Apr 2022 10:40:25 +0000 Subject: [PATCH 2/4] chore: format & lint --- packages/tracing/src/Tracer.ts | 6 +++--- packages/tracing/tests/unit/Tracer.test.ts | 12 ++++++------ packages/tracing/tests/unit/middy.test.ts | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/tracing/src/Tracer.ts b/packages/tracing/src/Tracer.ts index 115681a586..baaab6b641 100644 --- a/packages/tracing/src/Tracer.ts +++ b/packages/tracing/src/Tracer.ts @@ -350,7 +350,7 @@ class Tracer extends Utility implements TracerInterface { descriptor.value = ((event, context, callback) => { if (!this.isTracingEnabled()) { - return originalMethod?.apply(target, [event, context, callback]); + return originalMethod?.apply(target, [ event, context, callback ]); } return this.provider.captureAsyncFunc(`## ${process.env._HANDLER}`, async subsegment => { @@ -358,7 +358,7 @@ class Tracer extends Utility implements TracerInterface { this.addServiceNameAnnotation(); let result: unknown; try { - result = await originalMethod?.apply(target, [event, context, callback]); + result = await originalMethod?.apply(target, [ event, context, callback ]); this.addResponseAsMetadata(result, process.env._HANDLER); } catch (error) { this.addErrorAsMetadata(error as Error); @@ -594,7 +594,7 @@ class Tracer extends Utility implements TracerInterface { * Used internally during initialization. */ private getEnvVarsService(): EnvironmentVariablesService { - return this.envVarsService; + return this.envVarsService; } /** diff --git a/packages/tracing/tests/unit/Tracer.test.ts b/packages/tracing/tests/unit/Tracer.test.ts index 222216c517..28ac6a3213 100644 --- a/packages/tracing/tests/unit/Tracer.test.ts +++ b/packages/tracing/tests/unit/Tracer.test.ts @@ -84,10 +84,10 @@ describe('Class: Tracer', () => { // Assess expect(putAnnotationSpy).toBeCalledTimes(4); expect(putAnnotationSpy.mock.calls).toEqual([ - ['ColdStart', true], - ['ColdStart', false], - ['ColdStart', false], - ['ColdStart', false], + [ 'ColdStart', true ], + [ 'ColdStart', false ], + [ 'ColdStart', false ], + [ 'ColdStart', false ], ]); }); @@ -792,8 +792,8 @@ describe('Class: Tracer', () => { expect(putAnnotationSpy.mock.calls.filter(call => call[0] === 'ColdStart' )).toEqual([ - ['ColdStart', true], - ['ColdStart', false], + [ 'ColdStart', true ], + [ 'ColdStart', false ], ]); expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ name: '## index.handler', diff --git a/packages/tracing/tests/unit/middy.test.ts b/packages/tracing/tests/unit/middy.test.ts index 1580a119e6..8985bd330d 100644 --- a/packages/tracing/tests/unit/middy.test.ts +++ b/packages/tracing/tests/unit/middy.test.ts @@ -228,8 +228,8 @@ describe('Middy middleware', () => { expect(putAnnotationSpy.mock.calls.filter(call => call[0] === 'ColdStart' )).toEqual([ - ['ColdStart', true], - ['ColdStart', false], + [ 'ColdStart', true ], + [ 'ColdStart', false ], ]); expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({ name: '## index.handler', From cb35d3626738e0bf2ca84107e2eefb2bb515e068 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 14 Apr 2022 10:59:14 +0000 Subject: [PATCH 3/4] docs: added section to docs --- docs/core/tracer.md | 79 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index 8f24399847..7e9df61602 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -407,6 +407,85 @@ Use **`POWERTOOLS_TRACER_CAPTURE_ERROR=false`** environment variable to instruct 1. You might **return sensitive** information from exceptions, stack traces you might not control +### Reusing Tracer across your code + +Tracer keeps a copy of its configuration after the first initialization. This is useful for scenarios where you want to use Tracer in more than one location across your code base. + +=== "index.ts" + + ```typescript hl_lines="5" + import { Tracer } from '@aws-lambda-powertools/tracer'; + import middy from '@middy/core'; + import collectPayment from './payment'; + + const tracer = new Tracer({ serviceName: 'serverlessAirline' }); // (1) + + export const handler = middy(async (event: any, _context: any): Promise => { + await collectPayment(event.chargeId); + }).use(captureLambdaHandler(tracer)); + ``` + + 1. Since this is the first time Tracer is initialized, the settings are reused across all other Tracer instances +=== "payment.ts" + + ```typescript hl_lines="3" + import { Tracer } from '@aws-lambda-powertools/tracer'; + + const tracer = new Tracer(); // (1) + + const collectPayment = async (chargeId: string) => { + const mainSegment = tracer.getSegment(); // (2) + const subsegment = mainSegment.addNewSubsegment('### collectPayment'); + tracer.setSegment(subsegment); + + // This annotation will be done on the `### collectPayment` segment + tracer.putAnnotation('chargeId', chargeId); + + /* ... */ + + subsegment.close(); + tracer.setSegment(mainSegment); + }; + + export default collectPayment; + ``` + + 1. You don't need to pass any parameters here, this instance will have the same configuration as the first Tracer instance + 2. This is the main subsegment called `### index.handler` that was created by the `captureLambdaHandler` middleware +=== "Example Raw X-Ray Trace excerpt" + + ```json hl_lines="3 17 20 24" + { + "id": "22883fbc730e3a0b", + "name": "## index.handler", + "start_time": 1647956168.22749, + "end_time": 1647956169.0679862, + "annotations": { + "ColdStart": true + }, + "metadata": { + "default": { + "index.handler response": { + "body": "{\"message\":\"Payment collected for chargeId: 1234\"}", + "statusCode": 200 + } + } + }, + "subsegments": [ + { + "id": "24542b252e5c0756", + "name": "collectPayment", + "start_time": 1647956169.22749, + "end_time": 1647956169.32749, + "annotations": { + "chargeId": "1234" + } + } + ] + } + ``` + + ### 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). From 409c3444fcf61c4140cc0c905667e99d5acf2679 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 19 Apr 2022 15:46:59 +0200 Subject: [PATCH 4/4] docs: changed wording on configuration docs --- docs/core/tracer.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/tracer.md b/docs/core/tracer.md index 7e9df61602..0ca136c11d 100644 --- a/docs/core/tracer.md +++ b/docs/core/tracer.md @@ -418,14 +418,14 @@ Tracer keeps a copy of its configuration after the first initialization. This is import middy from '@middy/core'; import collectPayment from './payment'; - const tracer = new Tracer({ serviceName: 'serverlessAirline' }); // (1) + const tracer = new Tracer(); // (1) export const handler = middy(async (event: any, _context: any): Promise => { await collectPayment(event.chargeId); }).use(captureLambdaHandler(tracer)); ``` - 1. Since this is the first time Tracer is initialized, the settings are reused across all other Tracer instances + 1. Because of the way ESM modules work, in order to set up Tracer, you'll need to pass the configurations as [environment variables](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#environment-variables). === "payment.ts" ```typescript hl_lines="3" @@ -450,7 +450,7 @@ Tracer keeps a copy of its configuration after the first initialization. This is export default collectPayment; ``` - 1. You don't need to pass any parameters here, this instance will have the same configuration as the first Tracer instance + 1. You don't need to pass any parameters here, this instance will have the same configuration as the other Tracer instance 2. This is the main subsegment called `### index.handler` that was created by the `captureLambdaHandler` middleware === "Example Raw X-Ray Trace excerpt"