@@ -17,6 +17,7 @@ import {
1717 SpanStatusCode ,
1818 ROOT_CONTEXT ,
1919} from '@opentelemetry/api' ;
20+ import * as api from '@opentelemetry/api' ;
2021import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' ;
2122import { Instrumentation } from '@opentelemetry/instrumentation' ;
2223import { AwsInstrumentation , NormalizedRequest , NormalizedResponse } from '@opentelemetry/instrumentation-aws-sdk' ;
@@ -35,14 +36,14 @@ import {
3536import * as sinon from 'sinon' ;
3637import { AWSXRAY_TRACE_ID_HEADER , AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray' ;
3738import { Context } from 'aws-lambda' ;
38- import { SinonStub } from 'sinon' ;
3939import { Lambda } from '@aws-sdk/client-lambda' ;
4040import * as nock from 'nock' ;
4141import { ReadableSpan , Span as SDKSpan } from '@opentelemetry/sdk-trace-base' ;
4242import { getTestSpans } from '@opentelemetry/contrib-test-utils' ;
4343import { instrumentationConfigs } from '../../src/register' ;
4444import { LoggerProvider } from '@opentelemetry/api-logs' ;
4545import { STS } from '@aws-sdk/client-sts' ;
46+ import { getPropagator } from '@opentelemetry/auto-configuration-propagators' ;
4647
4748// It is assumed that bedrock.test.ts has already registered the
4849// necessary instrumentations for testing by calling:
@@ -71,6 +72,7 @@ const mockHeaders = {
7172 'x-test-header' : 'test-value' ,
7273 'content-type' : 'application/json' ,
7374} ;
75+ const mockCarrier = { lambdaEventHeaders : mockHeaders } ;
7476
7577const UNPATCHED_INSTRUMENTATIONS : Instrumentation [ ] = getNodeAutoInstrumentations ( instrumentationConfigs ) ;
7678
@@ -965,22 +967,28 @@ describe('InstrumentationPatchTest', () => {
965967} ) ;
966968
967969describe ( 'customExtractor' , ( ) => {
970+ let lambdaPropagator : api . TextMapPropagator ;
968971 const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID' ;
969- const MOCK_XRAY_TRACE_ID = '8a3c60f7d188f8fa79d48a391a778fa6' ;
970- const MOCK_XRAY_TRACE_ID_STR = '1-8a3c60f7-d188f8fa79d48a391a778fa6' ;
971- const MOCK_XRAY_PARENT_SPAN_ID = '53995c3f42cd8ad8' ;
972+ const MOCK_XRAY_TRACE_ID_0 = '8a3c0000d188f8fa79d48a391a770000' ;
973+ const MOCK_XRAY_TRACE_ID_1 = '8a3c0001d188f8fa79d48a391a770001' ;
974+ const MOCK_XRAY_TRACE_ID_STR_0 = '8a3c0000-d188f8fa79d48a391a770000' ;
975+ const MOCK_XRAY_TRACE_ID_STR_1 = '8a3c0001-d188f8fa79d48a391a770001' ;
976+ const MOCK_XRAY_PARENT_SPAN_ID_0 = '53995c3f42cd0000' ;
977+ const MOCK_XRAY_PARENT_SPAN_ID_1 = '53995c3f42cd0001' ;
972978 const MOCK_XRAY_LAMBDA_LINEAGE = 'Lineage=01cfa446:0' ;
973979
974980 const TRACE_ID_VERSION = '1' ; // Assuming TRACE_ID_VERSION is defined somewhere in the code
975981
976982 // Common part of the XRAY trace context
977- const MOCK_XRAY_TRACE_CONTEXT_COMMON = `Root=${ TRACE_ID_VERSION } -${ MOCK_XRAY_TRACE_ID_STR } ;Parent=${ MOCK_XRAY_PARENT_SPAN_ID } ` ;
983+ const MOCK_XRAY_TRACE_CONTEXT_0_COMMON = `Root=${ TRACE_ID_VERSION } -${ MOCK_XRAY_TRACE_ID_STR_0 } ;Parent=${ MOCK_XRAY_PARENT_SPAN_ID_0 } ` ;
984+ const MOCK_XRAY_TRACE_CONTEXT_1_COMMON = `Root=${ TRACE_ID_VERSION } -${ MOCK_XRAY_TRACE_ID_STR_1 } ;Parent=${ MOCK_XRAY_PARENT_SPAN_ID_1 } ` ;
978985
979986 // Different versions of the XRAY trace context
980- const MOCK_XRAY_TRACE_CONTEXT_SAMPLED = `${ MOCK_XRAY_TRACE_CONTEXT_COMMON } ;Sampled=1;${ MOCK_XRAY_LAMBDA_LINEAGE } ` ;
987+ const MOCK_XRAY_TRACE_CONTEXT_0_SAMPLED = `${ MOCK_XRAY_TRACE_CONTEXT_0_COMMON } ;Sampled=1;${ MOCK_XRAY_LAMBDA_LINEAGE } ` ;
988+ const MOCK_XRAY_TRACE_CONTEXT_1_UNSAMPLED = `${ MOCK_XRAY_TRACE_CONTEXT_1_COMMON } ;Sampled=0;${ MOCK_XRAY_LAMBDA_LINEAGE } ` ;
981989 // const MOCK_XRAY_TRACE_CONTEXT_PASSTHROUGH = (
982- // `Root=${TRACE_ID_VERSION}-${MOCK_XRAY_TRACE_ID_STR .slice(0, TRACE_ID_FIRST_PART_LENGTH)}` +
983- // `-${MOCK_XRAY_TRACE_ID_STR .slice(TRACE_ID_FIRST_PART_LENGTH)};${MOCK_XRAY_LAMBDA_LINEAGE}`
990+ // `Root=${TRACE_ID_VERSION}-${MOCK_XRAY_TRACE_ID_STR_0 .slice(0, TRACE_ID_FIRST_PART_LENGTH)}` +
991+ // `-${MOCK_XRAY_TRACE_ID_STR_0 .slice(TRACE_ID_FIRST_PART_LENGTH)};${MOCK_XRAY_LAMBDA_LINEAGE}`
984992 // );
985993
986994 // Create the W3C Trace Context (Sampled)
@@ -991,11 +999,22 @@ describe('customExtractor', () => {
991999 const MOCK_W3C_TRACE_STATE_VALUE = 'test_value' ;
9921000 const MOCK_TRACE_STATE = `${ MOCK_W3C_TRACE_STATE_KEY } =${ MOCK_W3C_TRACE_STATE_VALUE } ,foo=1,bar=2` ;
9931001
994- let awsPropagatorStub : SinonStub ;
995- let traceGetSpanStub : SinonStub ;
996- // let propagationStub: SinonStub;
1002+ let awsPropagatorSpy : sinon . SinonSpy ;
1003+ let traceGetSpanSpy : sinon . SinonSpy ;
1004+ let propagationStub : sinon . SinonStub ;
1005+
1006+ before ( ( ) => {
1007+ process . env . OTEL_PROPAGATORS = 'baggage,xray,tracecontext' ;
1008+ lambdaPropagator = getPropagator ( ) ;
1009+ delete process . env . OTEL_PROPAGATORS ;
1010+ } ) ;
9971011
9981012 beforeEach ( ( ) => {
1013+ propagationStub = sinon
1014+ . stub ( propagation , 'extract' )
1015+ . callsFake ( ( context : OtelContext , carrier : unknown , getter ?: api . TextMapGetter < unknown > | undefined ) => {
1016+ return lambdaPropagator . extract ( context , carrier , getter ! ) ;
1017+ } ) ;
9991018 // Clear environment variables before each test
10001019 delete process . env [ traceContextEnvironmentKey ] ;
10011020 } ) ;
@@ -1005,41 +1024,145 @@ describe('customExtractor', () => {
10051024 sinon . restore ( ) ;
10061025 } ) ;
10071026
1008- it ( 'should extract context from lambda trace header when present' , ( ) => {
1009- const mockLambdaTraceHeader = MOCK_XRAY_TRACE_CONTEXT_SAMPLED ;
1010- process . env [ traceContextEnvironmentKey ] = mockLambdaTraceHeader ;
1027+ it ( 'should extract context from handler context xRayTraceId property when present' , ( ) => {
1028+ const mockLambdaTraceHeader = MOCK_XRAY_TRACE_CONTEXT_0_SAMPLED ;
1029+ const mockHandlerContext = {
1030+ xRayTraceId : mockLambdaTraceHeader ,
1031+ } as unknown as Context ;
1032+
1033+ // Mock of the Span Context for validation
1034+ const mockParentSpanContext : api . SpanContext = {
1035+ isRemote : true ,
1036+ traceFlags : 1 ,
1037+ traceId : MOCK_XRAY_TRACE_ID_0 ,
1038+ spanId : MOCK_XRAY_PARENT_SPAN_ID_0 ,
1039+ } ;
10111040
1012- const mockParentContext = { } as OtelContext ;
1041+ sinon . stub ( otelContext , 'active' ) . returns ( ROOT_CONTEXT ) ;
1042+ awsPropagatorSpy = sinon . spy ( AWSXRayPropagator . prototype , 'extract' ) ;
1043+ traceGetSpanSpy = sinon . spy ( trace , 'getSpan' ) ;
10131044
1014- // Partial mock of the Span object
1015- const mockSpan : Partial < Span > = {
1016- spanContext : sinon . stub ( ) . returns ( {
1017- traceId : MOCK_XRAY_TRACE_ID ,
1018- spanId : MOCK_XRAY_PARENT_SPAN_ID ,
1019- } ) ,
1020- } ;
1045+ // Call the customExtractor function
1046+ const event = { headers : { } } ;
1047+ const result = customExtractor ( event , mockHandlerContext ) ;
10211048
1022- // Stub awsPropagator.extract to return the mockParentContext
1023- awsPropagatorStub = sinon . stub ( AWSXRayPropagator . prototype , 'extract' ) . returns ( mockParentContext ) ;
1049+ // Assertions
1050+ expect ( awsPropagatorSpy . calledOnce ) . toBe ( true ) ;
1051+ expect (
1052+ awsPropagatorSpy . calledWith (
1053+ sinon . match . any ,
1054+ {
1055+ lambdaEventHeaders : event . headers ,
1056+ lambdaContext : mockHandlerContext ,
1057+ } ,
1058+ sinon . match . any
1059+ )
1060+ ) . toBe ( true ) ;
1061+ expect ( traceGetSpanSpy . calledOnce ) . toBe ( true ) ;
1062+ expect ( trace . getSpan ( result ) ?. spanContext ( ) ) . toEqual ( mockParentSpanContext ) ; // Should return the parent context when valid
1063+ } ) ;
1064+
1065+ it ( 'should prioritize extract context from handler context xRayTraceId property instead of environment variable' , ( ) => {
1066+ const mockLambdaContextTraceHeader = MOCK_XRAY_TRACE_CONTEXT_1_UNSAMPLED ;
1067+ const mockLambdEnvVarTraceHeader = MOCK_XRAY_TRACE_CONTEXT_0_SAMPLED ;
1068+
1069+ process . env [ traceContextEnvironmentKey ] = mockLambdEnvVarTraceHeader ;
1070+
1071+ const mockHandlerContext = {
1072+ xRayTraceId : mockLambdaContextTraceHeader ,
1073+ } as unknown as Context ;
1074+
1075+ // Mock of the Span Context for validation
1076+ const mockParentSpanContext : api . SpanContext = {
1077+ isRemote : true ,
1078+ traceFlags : 0 ,
1079+ traceId : MOCK_XRAY_TRACE_ID_1 ,
1080+ spanId : MOCK_XRAY_PARENT_SPAN_ID_1 ,
1081+ } ;
10241082
1025- // Stub trace.getSpan to return the mock span
1026- traceGetSpanStub = sinon . stub ( trace , 'getSpan' ) . returns ( mockSpan as Span ) ;
1083+ sinon . stub ( otelContext , 'active' ) . returns ( ROOT_CONTEXT ) ;
1084+ awsPropagatorSpy = sinon . spy ( AWSXRayPropagator . prototype , 'extract' ) ;
1085+ traceGetSpanSpy = sinon . spy ( trace , 'getSpan' ) ;
10271086
10281087 // Call the customExtractor function
10291088 const event = { headers : { } } ;
1089+ const result = customExtractor ( event , mockHandlerContext ) ;
1090+
1091+ // Assertions
1092+ expect ( awsPropagatorSpy . calledOnce ) . toBe ( true ) ;
1093+ expect (
1094+ awsPropagatorSpy . calledWith (
1095+ sinon . match . any ,
1096+ {
1097+ lambdaEventHeaders : event . headers ,
1098+ lambdaContext : mockHandlerContext ,
1099+ } ,
1100+ sinon . match . any
1101+ )
1102+ ) . toBe ( true ) ;
1103+ expect ( traceGetSpanSpy . calledOnce ) . toBe ( true ) ;
1104+ expect ( trace . getSpan ( result ) ?. spanContext ( ) ) . toEqual ( mockParentSpanContext ) ; // Should return the parent context when valid
1105+ } ) ;
1106+
1107+ it ( 'should fallback to environment variable when handler context xRayTraceId is not available' , ( ) => {
1108+ const mockLambdaTraceHeader = MOCK_XRAY_TRACE_CONTEXT_0_SAMPLED ;
1109+ process . env [ traceContextEnvironmentKey ] = mockLambdaTraceHeader ;
1110+
1111+ // Mock of the Span Context for validation
1112+ const mockParentSpanContext : api . SpanContext = {
1113+ isRemote : true ,
1114+ traceFlags : 1 ,
1115+ traceId : MOCK_XRAY_TRACE_ID_0 ,
1116+ spanId : MOCK_XRAY_PARENT_SPAN_ID_0 ,
1117+ } ;
1118+
1119+ sinon . stub ( otelContext , 'active' ) . returns ( ROOT_CONTEXT ) ;
1120+ awsPropagatorSpy = sinon . spy ( AWSXRayPropagator . prototype , 'extract' ) ;
1121+ traceGetSpanSpy = sinon . spy ( trace , 'getSpan' ) ;
1122+
1123+ // Call the customExtractor function with handler context without xRayTraceId
1124+ const event = { headers : { } } ;
10301125 const result = customExtractor ( event , { } as Context ) ;
10311126
10321127 // Assertions
1033- expect ( awsPropagatorStub . calledOnce ) . toBe ( true ) ;
1128+ expect ( awsPropagatorSpy . calledOnce ) . toBe ( true ) ;
10341129 expect (
1035- awsPropagatorStub . calledWith (
1130+ awsPropagatorSpy . calledWith (
10361131 sinon . match . any ,
1037- { [ AWSXRAY_TRACE_ID_HEADER ] : mockLambdaTraceHeader } ,
1132+ {
1133+ lambdaEventHeaders : event . headers ,
1134+ lambdaContext : { } ,
1135+ } ,
10381136 sinon . match . any
10391137 )
10401138 ) . toBe ( true ) ;
1041- expect ( traceGetSpanStub . calledOnce ) . toBe ( true ) ;
1042- expect ( result ) . toEqual ( mockParentContext ) ; // Should return the parent context when valid
1139+ expect ( traceGetSpanSpy . calledOnce ) . toBe ( true ) ;
1140+ expect ( trace . getSpan ( result ) ?. spanContext ( ) ) . toEqual ( mockParentSpanContext ) ; // Should return the parent context when valid
1141+ } ) ;
1142+
1143+ it ( 'should handle undefined handler context and fallback to environment variable' , ( ) => {
1144+ const mockLambdaTraceHeader = MOCK_XRAY_TRACE_CONTEXT_0_SAMPLED ;
1145+ process . env [ traceContextEnvironmentKey ] = mockLambdaTraceHeader ;
1146+
1147+ // Mock of the Span Context for validation
1148+ const mockParentSpanContext : api . SpanContext = {
1149+ isRemote : true ,
1150+ traceFlags : 1 ,
1151+ traceId : MOCK_XRAY_TRACE_ID_0 ,
1152+ spanId : MOCK_XRAY_PARENT_SPAN_ID_0 ,
1153+ } ;
1154+
1155+ sinon . stub ( otelContext , 'active' ) . returns ( ROOT_CONTEXT ) ;
1156+ awsPropagatorSpy = sinon . spy ( AWSXRayPropagator . prototype , 'extract' ) ;
1157+ traceGetSpanSpy = sinon . spy ( trace , 'getSpan' ) ;
1158+
1159+ // Call the customExtractor function with undefined handler context
1160+ const event = { headers : { } } ;
1161+ const result = customExtractor ( event , undefined as any ) ;
1162+
1163+ // Should only be called once (for environment variable)
1164+ expect ( awsPropagatorSpy . calledOnce ) . toBe ( true ) ;
1165+ expect ( trace . getSpan ( result ) ?. spanContext ( ) ) . toEqual ( mockParentSpanContext ) ; // Should return the valid parent context
10431166 } ) ;
10441167
10451168 it ( 'should extract context from HTTP headers when lambda trace header is not present' , ( ) => {
@@ -1056,27 +1179,36 @@ describe('customExtractor', () => {
10561179 } , // Empty function that returns undefined
10571180 } as unknown as OtelContext ;
10581181
1059- const propagationStub = sinon . stub ( propagation , 'extract' ) . returns ( mockExtractedContext ) ;
1182+ propagationStub . returns ( mockExtractedContext ) ;
10601183
10611184 // Call the customExtractor function
10621185 const mockHttpHeaders = event . headers ;
10631186 customExtractor ( event , { } as Context ) ;
10641187
1065- expect ( propagationStub . calledWith ( sinon . match . any , mockHttpHeaders , sinon . match . any ) ) . toBe ( true ) ;
1188+ expect (
1189+ propagationStub . calledWith (
1190+ sinon . match . any ,
1191+ {
1192+ lambdaEventHeaders : mockHttpHeaders ,
1193+ lambdaContext : { } ,
1194+ } ,
1195+ sinon . match . any
1196+ )
1197+ ) . toBe ( true ) ;
10661198 } ) ;
10671199
10681200 it ( 'should return all header keys from the carrier' , ( ) => {
1069- const keys = headerGetter . keys ( mockHeaders ) ;
1201+ const keys = headerGetter . keys ( mockCarrier ) ;
10701202 expect ( keys ) . toEqual ( [ 'x-test-header' , 'content-type' ] ) ;
10711203 } ) ;
10721204
10731205 it ( 'should return the correct header value for a given key' , ( ) => {
1074- const headerValue = headerGetter . get ( mockHeaders , 'x-test-header' ) ;
1206+ const headerValue = headerGetter . get ( mockCarrier , 'x-test-header' ) ;
10751207 expect ( headerValue ) . toBe ( 'test-value' ) ;
10761208 } ) ;
10771209
10781210 it ( 'should return undefined for a key that does not exist' , ( ) => {
1079- const headerValue = headerGetter . get ( mockHeaders , 'non-existent-header' ) ;
1211+ const headerValue = headerGetter . get ( mockCarrier , 'non-existent-header' ) ;
10801212 expect ( headerValue ) . toBeUndefined ( ) ;
10811213 } ) ;
10821214} ) ;
0 commit comments