Skip to content

Commit a6fb5f0

Browse files
Handle otel context failure gracefully
1 parent 9cfb85c commit a6fb5f0

File tree

2 files changed

+90
-50
lines changed

2 files changed

+90
-50
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,17 +406,28 @@ function patchAwsSdkInstrumentation(instrumentation: Instrumentation): void {
406406
this.middlewareStack?.add(
407407
(next: any, context: any) => async (middlewareArgs: any) => {
408408
const activeContext = otelContext.active();
409+
if (!activeContext) {
410+
return await next(middlewareArgs);
411+
}
412+
409413
// Skip credential extraction if this is a nested call from another credential extraction
410414
// This prevents infinite recursion when credential providers make AWS API calls
411-
if (activeContext.getValue(SKIP_CREDENTIAL_CAPTURE_KEY)) {
415+
if (activeContext.getValue?.(SKIP_CREDENTIAL_CAPTURE_KEY)) {
412416
return await next(middlewareArgs);
413417
}
414418
const span = trace.getSpan(activeContext);
415419

416420
if (span) {
417421
// suppressTracing prevents span generation for internal credential extraction calls
418422
// which are implementation details and not relevant to the application's telemetry
419-
const suppressedContext = suppressTracing(activeContext).setValue(SKIP_CREDENTIAL_CAPTURE_KEY, true);
423+
let suppressedContext;
424+
try {
425+
suppressedContext = suppressTracing(activeContext);
426+
suppressedContext.setValue(SKIP_CREDENTIAL_CAPTURE_KEY, true);
427+
} catch {
428+
return await next(middlewareArgs);
429+
}
430+
420431
await otelContext.with(suppressedContext, async () => {
421432
try {
422433
const credsProvider = this.config.credentials;

aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -692,59 +692,88 @@ describe('InstrumentationPatchTest', () => {
692692
});
693693
});
694694

695-
it('Prevents recursion when credentials provider makes STS calls', async () => {
696-
// Track credentials provider calls and skip flag state
697-
let credentialsProviderCallCount = 0;
698-
let skipCredentialCaptureValue = false;
699-
700-
// Mock span for attribute capture
701-
const mockSpan = { setAttribute: sinon.stub() };
702-
sinon.stub(trace, 'getSpan').returns(mockSpan as unknown as Span);
703-
704-
// Mock OpenTelemetry context to track skip-credential-capture flag
705-
const mockContext = {
706-
getValue: (key: symbol) =>
707-
key.toString().includes('skip-credential-capture') ? skipCredentialCaptureValue : undefined,
708-
setValue: (key: symbol, value: any) => {
709-
if (key.toString().includes('skip-credential-capture')) skipCredentialCaptureValue = value;
710-
return mockContext;
711-
},
712-
deleteValue: () => mockContext,
713-
};
714-
sinon.stub(context, 'active').returns(mockContext);
715-
716-
// Capture middleware added to stack
717-
const middlewareStack: any[] = [];
718-
const recursiveSdkSend = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS)
719-
['_getV3SmithyClientSendPatch'](() => Promise.resolve())
720-
.bind({
721-
middlewareStack: { add: (middleware: any, config: any) => middlewareStack.push([middleware, config]) },
722-
config: {
723-
// Credentials provider that recursively calls SDK (simulates STS)
724-
credentials: async () => {
725-
credentialsProviderCallCount++;
726-
await recursiveSdkSend({}, null);
727-
return { accessKeyId: 'test-access-key' };
728-
},
729-
region: () => Promise.resolve('us-west-2'),
695+
describe('credential extraction failure handling', () => {
696+
let credentialsProviderCallCount: number;
697+
let mockSpan: any;
698+
let middlewareStack: any[];
699+
let recursiveSdkSend: any;
700+
let createSdkSend: (recursive?: boolean) => any;
701+
702+
beforeEach(() => {
703+
credentialsProviderCallCount = 0;
704+
mockSpan = { setAttribute: sinon.stub() };
705+
sinon.stub(trace, 'getSpan').returns(mockSpan as unknown as Span);
706+
middlewareStack = [];
707+
708+
createSdkSend = () => {
709+
return extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS)
710+
['_getV3SmithyClientSendPatch'](() => Promise.resolve())
711+
.bind({
712+
middlewareStack: { add: (middleware: any, config: any) => middlewareStack.push([middleware, config]) },
713+
config: {
714+
credentials: async () => {
715+
credentialsProviderCallCount++;
716+
await recursiveSdkSend({}, null);
717+
return { accessKeyId: 'test-access-key' };
718+
},
719+
region: () => Promise.resolve('us-west-2'),
720+
},
721+
});
722+
};
723+
});
724+
725+
afterEach(() => {
726+
sinon.restore();
727+
});
728+
729+
it('Prevents recursion when credentials provider makes STS calls', async () => {
730+
let skipCredentialCaptureValue = false;
731+
const mockContext = {
732+
getValue: (key: symbol) =>
733+
key.toString().includes('skip-credential-capture') ? skipCredentialCaptureValue : undefined,
734+
setValue: (key: symbol, value: any) => {
735+
if (key.toString().includes('skip-credential-capture')) skipCredentialCaptureValue = value;
736+
return mockContext;
730737
},
731-
});
738+
deleteValue: () => mockContext,
739+
};
740+
sinon.stub(context, 'active').returns(mockContext);
741+
recursiveSdkSend = createSdkSend();
732742

733-
// Initial SDK call triggers middleware setup
734-
await recursiveSdkSend({}, null);
735-
expect(skipCredentialCaptureValue).toBe(false);
743+
await recursiveSdkSend({}, null);
744+
await middlewareStack[1][0](() => Promise.resolve(), null)({});
736745

737-
// Execute credentials extraction middleware
738-
await middlewareStack[1][0](() => Promise.resolve(), null)({});
746+
expect(credentialsProviderCallCount).toBe(1);
747+
expect(skipCredentialCaptureValue).toBe(true);
748+
expect(
749+
mockSpan.setAttribute.calledWith(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, 'test-access-key')
750+
).toBeTruthy();
751+
});
752+
753+
it('Handles undefined active context gracefully', async () => {
754+
sinon.stub(context, 'active').returns(undefined as any);
755+
recursiveSdkSend = createSdkSend();
739756

740-
// Verify recursion prevention: only one credentials call despite recursive setup
741-
expect(credentialsProviderCallCount).toBe(1);
742-
expect(skipCredentialCaptureValue).toBe(true);
743-
expect(
744-
mockSpan.setAttribute.calledWith(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, 'test-access-key')
745-
).toBeTruthy();
757+
await recursiveSdkSend({}, null);
758+
await middlewareStack[1][0](() => Promise.resolve(), null)({});
759+
expect(credentialsProviderCallCount).toBe(0);
760+
});
746761

747-
sinon.restore();
762+
it('Handles setValue failure gracefully', async () => {
763+
const mockContext = {
764+
getValue: () => false,
765+
setValue: () => {
766+
throw new Error('setValue failed');
767+
},
768+
deleteValue: () => mockContext,
769+
};
770+
sinon.stub(context, 'active').returns(mockContext);
771+
recursiveSdkSend = createSdkSend();
772+
773+
await recursiveSdkSend({}, null);
774+
await middlewareStack[1][0](() => Promise.resolve(), null)({});
775+
expect(credentialsProviderCallCount).toBe(0);
776+
});
748777
});
749778

750779
it('injects trace context header into request via propagator', async () => {

0 commit comments

Comments
 (0)