Skip to content

Commit fd02acb

Browse files
fix(tracer): avoid throwing errors in manual instrumentation when running outside of AWS Lambda (#442)
* fix: added local testing behavior * fix: correctly disable x-ray-sdk when tracer is disabled * Update packages/tracing/tests/unit/Tracer.test.ts Co-authored-by: Sara Gerion <[email protected]> * Update packages/tracing/tests/unit/Tracer.test.ts Co-authored-by: Sara Gerion <[email protected]> * Update packages/tracing/tests/unit/Tracer.test.ts Co-authored-by: Sara Gerion <[email protected]> * Update packages/tracing/tests/unit/Tracer.test.ts Co-authored-by: Sara Gerion <[email protected]> Co-authored-by: Sara Gerion <[email protected]>
1 parent b0b1cec commit fd02acb

File tree

2 files changed

+60
-20
lines changed

2 files changed

+60
-20
lines changed

Diff for: packages/tracing/src/Tracer.ts

+25-16
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ class Tracer implements TracerInterface {
130130
public constructor(options: TracerOptions = {}) {
131131
this.setOptions(options);
132132
this.provider = new ProviderService();
133+
if (this.isTracingEnabled() === false) {
134+
// Tell x-ray-sdk to not throw an error if context is missing but tracing is disabled
135+
this.provider.setContextMissingStrategy(() => ({}));
136+
}
133137
}
134138

135139
/**
@@ -140,7 +144,7 @@ class Tracer implements TracerInterface {
140144
* @param error - Error to serialize as metadata
141145
*/
142146
public addErrorAsMetadata(error: Error): void {
143-
if (this.tracingEnabled === false) {
147+
if (this.isTracingEnabled() === false) {
144148
return;
145149
}
146150

@@ -163,7 +167,7 @@ class Tracer implements TracerInterface {
163167
* @param methodName - Name of the method that is being traced
164168
*/
165169
public addResponseAsMetadata(data?: unknown, methodName?: string): void {
166-
if (data === undefined || this.captureResponse === false || this.tracingEnabled === false) {
170+
if (data === undefined || this.captureResponse === false || this.isTracingEnabled() === false) {
167171
return;
168172
}
169173

@@ -175,7 +179,7 @@ class Tracer implements TracerInterface {
175179
*
176180
*/
177181
public addServiceNameAnnotation(): void {
178-
if (this.tracingEnabled === false || this.serviceName === undefined) {
182+
if (this.isTracingEnabled() === false || this.serviceName === undefined) {
179183
return;
180184
}
181185
this.putAnnotation('Service', this.serviceName);
@@ -191,7 +195,7 @@ class Tracer implements TracerInterface {
191195
* @see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html
192196
*/
193197
public annotateColdStart(): void {
194-
if (this.tracingEnabled === true) {
198+
if (this.isTracingEnabled() === true) {
195199
this.putAnnotation('ColdStart', Tracer.coldStart);
196200
}
197201
if (Tracer.coldStart === true) {
@@ -222,7 +226,7 @@ class Tracer implements TracerInterface {
222226
* @returns AWS - Instrumented AWS SDK
223227
*/
224228
public captureAWS<T>(aws: T): T {
225-
if (this.tracingEnabled === false) return aws;
229+
if (this.isTracingEnabled() === false) return aws;
226230

227231
return this.provider.captureAWS(aws);
228232
}
@@ -251,7 +255,7 @@ class Tracer implements TracerInterface {
251255
* @returns service - Instrumented AWS SDK v2 client
252256
*/
253257
public captureAWSClient<T>(service: T): T {
254-
if (this.tracingEnabled === false) return service;
258+
if (this.isTracingEnabled() === false) return service;
255259

256260
return this.provider.captureAWSClient(service);
257261
}
@@ -281,7 +285,7 @@ class Tracer implements TracerInterface {
281285
* @returns service - Instrumented AWS SDK v3 client
282286
*/
283287
public captureAWSv3Client<T>(service: T): T {
284-
if (this.tracingEnabled === false) return service;
288+
if (this.isTracingEnabled() === false) return service;
285289

286290
return this.provider.captureAWSv3Client(service);
287291
}
@@ -322,7 +326,7 @@ class Tracer implements TracerInterface {
322326
const originalMethod = descriptor.value;
323327

324328
descriptor.value = ((event, context, callback) => {
325-
if (this.tracingEnabled === false) {
329+
if (this.isTracingEnabled() === false) {
326330
return originalMethod?.apply(target, [ event, context, callback ]);
327331
}
328332

@@ -389,7 +393,7 @@ class Tracer implements TracerInterface {
389393
const originalMethod = descriptor.value;
390394

391395
descriptor.value = (...args: unknown[]) => {
392-
if (this.tracingEnabled === false) {
396+
if (this.isTracingEnabled() === false) {
393397
return originalMethod?.apply(target, [...args]);
394398
}
395399

@@ -458,11 +462,14 @@ class Tracer implements TracerInterface {
458462
* @returns segment - The active segment or subsegment in the current scope.
459463
*/
460464
public getSegment(): Segment | Subsegment {
461-
const segment = this.provider.getSegment();
465+
if (this.isTracingEnabled() === false) {
466+
return new Subsegment('## Dummy segment');
467+
}
468+
const segment = this.provider.getSegment();
462469
if (segment === undefined) {
463470
throw new Error('Failed to get the current sub/segment from the context.');
464471
}
465-
472+
466473
return segment;
467474
}
468475

@@ -498,7 +505,7 @@ class Tracer implements TracerInterface {
498505
* @param value - Value for annotation
499506
*/
500507
public putAnnotation(key: string, value: string | number | boolean): void {
501-
if (this.tracingEnabled === false) return;
508+
if (this.isTracingEnabled() === false) return;
502509

503510
const document = this.getSegment();
504511
if (document instanceof Segment) {
@@ -531,7 +538,7 @@ class Tracer implements TracerInterface {
531538
* @param timestamp - Namespace that metadata will lie under, if none is passed it will use the serviceName
532539
*/
533540
public putMetadata(key: string, value: unknown, namespace?: string | undefined): void {
534-
if (this.tracingEnabled === false) return;
541+
if (this.isTracingEnabled() === false) return;
535542

536543
const document = this.getSegment();
537544
if (document instanceof Segment) {
@@ -567,6 +574,8 @@ class Tracer implements TracerInterface {
567574
* @param segment - Subsegment to set as the current segment
568575
*/
569576
public setSegment(segment: Segment | Subsegment): void {
577+
if (this.isTracingEnabled() === false) return;
578+
570579
return this.provider.setSegment(segment);
571580
}
572581

@@ -730,21 +739,21 @@ class Tracer implements TracerInterface {
730739
private setTracingEnabled(enabled?: boolean): void {
731740
if (enabled !== undefined && enabled === false) {
732741
this.tracingEnabled = enabled;
733-
742+
734743
return;
735744
}
736745

737746
const customConfigValue = this.getCustomConfigService()?.getTracingEnabled();
738747
if (customConfigValue !== undefined && customConfigValue.toLowerCase() === 'false') {
739748
this.tracingEnabled = false;
740-
749+
741750
return;
742751
}
743752

744753
const envVarsValue = this.getEnvVarsService()?.getTracingEnabled();
745754
if (envVarsValue.toLowerCase() === 'false') {
746755
this.tracingEnabled = false;
747-
756+
748757
return;
749758
}
750759

Diff for: packages/tracing/tests/unit/Tracer.test.ts

+35-4
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ describe('Class: Tracer', () => {
274274

275275
describe('Method: getSegment', () => {
276276

277-
test('when called outside of a namespace or without parent segment, it throws an error', () => {
277+
test('when called outside of a namespace or without parent segment, and tracing is enabled, it throws an error', () => {
278278

279279
// Prepare
280280
const tracer: Tracer = new Tracer();
@@ -286,7 +286,7 @@ describe('Class: Tracer', () => {
286286

287287
});
288288

289-
test('when called outside of a namespace or without parent segment, it throws an error', () => {
289+
test('when called and no segment is returned, while tracing is enabled, it throws an error', () => {
290290

291291
// Prepare
292292
const tracer: Tracer = new Tracer();
@@ -298,7 +298,22 @@ describe('Class: Tracer', () => {
298298
}).toThrow('Failed to get the current sub/segment from the context.');
299299

300300
});
301-
301+
302+
test('when called outside of a namespace or without parent segment, and tracing is disabled, it returns a dummy subsegment', () => {
303+
304+
// Prepare
305+
delete process.env.AWS_EXECUTION_ENV; // This will disable the tracer, simulating local execution
306+
const tracer: Tracer = new Tracer();
307+
308+
// Act
309+
const segment = tracer.getSegment();
310+
311+
// Assess
312+
expect(segment).toBeInstanceOf(Subsegment);
313+
expect(segment.name).toBe('## Dummy segment');
314+
315+
});
316+
302317
test('when called within a namespace, it returns the parent segment', () => {
303318

304319
// Prepare
@@ -320,7 +335,7 @@ describe('Class: Tracer', () => {
320335
});
321336

322337
describe('Method: setSegment', () => {
323-
test('when called outside of a namespace or without parent segment, it throws an error', () => {
338+
test('when called outside of a namespace or without parent segment, and Tracer is enabled, it throws an error', () => {
324339

325340
// Prepare
326341
const tracer: Tracer = new Tracer();
@@ -332,6 +347,22 @@ describe('Class: Tracer', () => {
332347
}).toThrow('No context available. ns.run() or ns.bind() must be called first.');
333348
});
334349

350+
test('when called outside of a namespace or without parent segment, and tracing is disabled, it does nothing', () => {
351+
352+
// Prepare
353+
delete process.env.AWS_EXECUTION_ENV; // This will disable the tracer, simulating local execution
354+
const tracer: Tracer = new Tracer();
355+
const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment');
356+
357+
// Act
358+
const newSubsegment = new Subsegment('## foo.bar');
359+
tracer.setSegment(newSubsegment);
360+
361+
// Assess
362+
expect(setSegmentSpy).toBeCalledTimes(0);
363+
364+
});
365+
335366
test('when called within a namespace, it sets the segment', () => {
336367

337368
// Prepare

0 commit comments

Comments
 (0)