From d7c9b83461bd9c54853cbbb91ece8311efa3f584 Mon Sep 17 00:00:00 2001
From: Andrea Amorosi <dreamorosi@gmail.com>
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 <EnvironmentVariablesService> this.envVarsService;
+    return <EnvironmentVariablesService>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<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
           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<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
           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<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): Promise<TResult> {
           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<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): Promise<TResult> {
           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<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): Promise<TResult> {
           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 <dreamorosi@gmail.com>
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 <EnvironmentVariablesService>this.envVarsService;
+    return <EnvironmentVariablesService> 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 <dreamorosi@gmail.com>
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<void> => {
+        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 <dreamorosi@gmail.com>
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<void> => {
         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"