@@ -44,6 +44,10 @@ import { instrumentationConfigs } from '../../src/register';
4444import { LoggerProvider } from '@opentelemetry/api-logs' ;
4545import { STS } from '@aws-sdk/client-sts' ;
4646import { getPropagator } from '@opentelemetry/auto-configuration-propagators' ;
47+ import { BatchSpanProcessor , InMemorySpanExporter } from '@opentelemetry/sdk-trace-base' ;
48+ import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' ;
49+ import { W3CTraceContextPropagator } from '@opentelemetry/core' ;
50+ import * as path from 'path' ;
4751
4852// It is assumed that bedrock.test.ts has already registered the
4953// necessary instrumentations for testing by calling:
@@ -966,7 +970,6 @@ describe('InstrumentationPatchTest', () => {
966970} ) ;
967971
968972describe ( 'customExtractor' , ( ) => {
969- let lambdaPropagator : api . TextMapPropagator ;
970973 const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID' ;
971974 const MOCK_XRAY_TRACE_ID_0 = '8a3c0000d188f8fa79d48a391a770000' ;
972975 const MOCK_XRAY_TRACE_ID_1 = '8a3c0001d188f8fa79d48a391a770001' ;
@@ -1000,20 +1003,14 @@ describe('customExtractor', () => {
10001003
10011004 let awsPropagatorSpy : sinon . SinonSpy ;
10021005 let traceGetSpanSpy : sinon . SinonSpy ;
1003- let propagationStub : sinon . SinonStub ;
10041006
10051007 before ( ( ) => {
10061008 process . env . OTEL_PROPAGATORS = 'baggage,xray,tracecontext' ;
1007- lambdaPropagator = getPropagator ( ) ;
1009+ propagation . setGlobalPropagator ( getPropagator ( ) ) ;
10081010 delete process . env . OTEL_PROPAGATORS ;
10091011 } ) ;
10101012
10111013 beforeEach ( ( ) => {
1012- propagationStub = sinon
1013- . stub ( propagation , 'extract' )
1014- . callsFake ( ( context : OtelContext , carrier : unknown , getter ?: api . TextMapGetter < unknown > | undefined ) => {
1015- return lambdaPropagator . extract ( context , carrier , getter ! ) ;
1016- } ) ;
10171014 // Clear environment variables before each test
10181015 delete process . env [ traceContextEnvironmentKey ] ;
10191016 } ) ;
@@ -1145,19 +1142,14 @@ describe('customExtractor', () => {
11451142 tracestate : MOCK_TRACE_STATE ,
11461143 } ,
11471144 } ;
1148- const mockExtractedContext = {
1149- getValue : function ( ) {
1150- return undefined ;
1151- } , // Empty function that returns undefined
1152- } as unknown as OtelContext ;
1153-
1154- propagationStub . returns ( mockExtractedContext ) ;
11551145
11561146 // Call the customExtractor function
1157- const mockHttpHeaders = event . headers ;
1158- customExtractor ( event , { } as Context ) ;
1147+ const result = customExtractor ( event , { } as Context ) ;
1148+ const resultSpanContext = trace . getSpan ( result ) ?. spanContext ( ) ;
11591149
1160- expect ( propagationStub . calledWith ( sinon . match . any , mockHttpHeaders , sinon . match . any ) ) . toBe ( true ) ;
1150+ expect ( resultSpanContext ?. spanId ) . toEqual ( '00f067aa0ba902b7' ) ;
1151+ expect ( resultSpanContext ?. traceId ) . toEqual ( '4bf92f3577b34da6a3ce929d0e0e4736' ) ;
1152+ expect ( resultSpanContext ?. traceFlags ) . toEqual ( 1 ) ;
11611153 } ) ;
11621154
11631155 it ( 'should return all header keys from the carrier' , ( ) => {
@@ -1175,3 +1167,149 @@ describe('customExtractor', () => {
11751167 expect ( headerValue ) . toBeUndefined ( ) ;
11761168 } ) ;
11771169} ) ;
1170+
1171+ describe ( 'AWS Lambda Instrumentation Propagation' , ( ) => {
1172+ const memoryExporter = new InMemorySpanExporter ( ) ;
1173+ let oldEnv : NodeJS . ProcessEnv ;
1174+ let instrumentation : AwsLambdaInstrumentation ;
1175+
1176+ const serializeSpanContext = ( spanContext : api . SpanContext , propagator : api . TextMapPropagator ) : string => {
1177+ let serialized = '' ;
1178+ propagator . inject (
1179+ trace . setSpan ( context . active ( ) , trace . wrapSpanContext ( spanContext ) ) ,
1180+ { } ,
1181+ {
1182+ set ( carrier : any , key : string , value : string ) {
1183+ serialized = value ;
1184+ } ,
1185+ }
1186+ ) ;
1187+ return serialized ;
1188+ } ;
1189+
1190+ const sampledAwsSpanContext : api . SpanContext = {
1191+ traceId : '11111111111111110000000000000000' ,
1192+ spanId : '9999999900000000' ,
1193+ traceFlags : 1 ,
1194+ isRemote : true ,
1195+ } ;
1196+ const sampledAwsHeader = serializeSpanContext ( sampledAwsSpanContext , new AWSXRayPropagator ( ) ) ;
1197+
1198+ const sampledAwsSpanContext2 : api . SpanContext = {
1199+ traceId : '11111111111111110000000000000022' ,
1200+ spanId : '9999999900000022' ,
1201+ traceFlags : 1 ,
1202+ isRemote : true ,
1203+ } ;
1204+ const sampledAwsHeader2 = serializeSpanContext ( sampledAwsSpanContext2 , new AWSXRayPropagator ( ) ) ;
1205+
1206+ const sampledGenericSpanContext : api . SpanContext = {
1207+ traceId : '00000000000000001111111111111111' ,
1208+ spanId : '0000000099999999' ,
1209+ traceFlags : 1 ,
1210+ isRemote : true ,
1211+ } ;
1212+ const sampledGenericSpan = serializeSpanContext ( sampledGenericSpanContext , new W3CTraceContextPropagator ( ) ) ;
1213+
1214+ const lambdaCtx = {
1215+ functionName : 'my_function' ,
1216+ invokedFunctionArn : 'my_arn' ,
1217+ awsRequestId : 'aws_request_id' ,
1218+ xRayTraceId : sampledAwsHeader ,
1219+ } as unknown as Context ;
1220+
1221+ const initializeHandler = ( propagators : string ) => {
1222+ process . env . LAMBDA_TASK_ROOT = path . resolve ( __dirname , '.' ) ;
1223+ process . env . _HANDLER = 'lambda-test-handlers/async.handler' ;
1224+
1225+ propagation . disable ( ) ;
1226+ process . env . OTEL_PROPAGATORS = propagators ;
1227+ propagation . setGlobalPropagator ( getPropagator ( ) ) ;
1228+ delete process . env . OTEL_PROPAGATORS ;
1229+
1230+ const provider = new NodeTracerProvider ( {
1231+ spanProcessors : [ new BatchSpanProcessor ( memoryExporter ) ] ,
1232+ } ) ;
1233+
1234+ instrumentation = new AwsLambdaInstrumentation ( {
1235+ eventContextExtractor : customExtractor ,
1236+ } ) ;
1237+ applyInstrumentationPatches ( [ instrumentation ] ) ;
1238+ instrumentation . setTracerProvider ( provider ) ;
1239+ } ;
1240+
1241+ const lambdaRequire = ( module : string ) => require ( path . resolve ( __dirname , '.' , module ) ) ;
1242+
1243+ beforeEach ( ( ) => {
1244+ oldEnv = { ...process . env } ;
1245+ } ) ;
1246+
1247+ afterEach ( ( ) => {
1248+ process . env = oldEnv ;
1249+ instrumentation . disable ( ) ;
1250+ memoryExporter . reset ( ) ;
1251+ } ) ;
1252+
1253+ it ( 'Prioritizes W3C Header over X-Ray Trace ID from Lambda Context' , async ( ) => {
1254+ initializeHandler ( 'baggage,xray,tracecontext' ) ;
1255+
1256+ const lambdaEvent = {
1257+ headers : {
1258+ traceparent : sampledGenericSpan ,
1259+ } ,
1260+ } ;
1261+
1262+ const result = await lambdaRequire ( 'lambda-test-handlers/async' ) . handler ( lambdaEvent , lambdaCtx ) ;
1263+
1264+ expect ( result ) . toBe ( 'hello world' ) ;
1265+ const spans = memoryExporter . getFinishedSpans ( ) ;
1266+ expect ( spans . length ) . toBe ( 1 ) ;
1267+ const [ span ] = spans ;
1268+ expect ( span . spanContext ( ) . traceId ) . toBe ( sampledGenericSpanContext . traceId ) ;
1269+ expect ( span . parentSpanId ) . toBe ( sampledGenericSpanContext . spanId ) ;
1270+ } ) ;
1271+
1272+ it ( 'Prioritizes X-Ray Trace ID from Lambda Context over W3C Header' , async ( ) => {
1273+ initializeHandler ( 'baggage,tracecontext,xray' ) ;
1274+
1275+ const lambdaEvent = {
1276+ headers : {
1277+ traceparent : sampledGenericSpan ,
1278+ } ,
1279+ } ;
1280+
1281+ const result = await lambdaRequire ( 'lambda-test-handlers/async' ) . handler ( lambdaEvent , lambdaCtx ) ;
1282+
1283+ expect ( result ) . toBe ( 'hello world' ) ;
1284+ const spans = memoryExporter . getFinishedSpans ( ) ;
1285+ expect ( spans . length ) . toBe ( 1 ) ;
1286+ const [ span ] = spans ;
1287+ expect ( span . spanContext ( ) . traceId ) . toBe ( sampledAwsSpanContext . traceId ) ;
1288+ expect ( span . parentSpanId ) . toBe ( sampledAwsSpanContext . spanId ) ;
1289+ } ) ;
1290+
1291+ it ( 'Prioritizes X-Ray Trace ID from Lambda Context over X-Ray Trace ID from Lambda Event headers' , async ( ) => {
1292+ initializeHandler ( 'xray' ) ;
1293+
1294+ const lambdaEvent = {
1295+ headers : {
1296+ 'x-amzn-trace-id' : sampledAwsHeader ,
1297+ } ,
1298+ } ;
1299+ const lambdaCtx2 = {
1300+ functionName : 'my_function' ,
1301+ invokedFunctionArn : 'my_arn' ,
1302+ awsRequestId : 'aws_request_id' ,
1303+ xRayTraceId : sampledAwsHeader2 ,
1304+ } as unknown as Context ;
1305+
1306+ const result = await lambdaRequire ( 'lambda-test-handlers/async' ) . handler ( lambdaEvent , lambdaCtx2 ) ;
1307+
1308+ expect ( result ) . toBe ( 'hello world' ) ;
1309+ const spans = memoryExporter . getFinishedSpans ( ) ;
1310+ expect ( spans . length ) . toBe ( 1 ) ;
1311+ const [ span ] = spans ;
1312+ expect ( span . spanContext ( ) . traceId ) . toBe ( sampledAwsSpanContext2 . traceId ) ;
1313+ expect ( span . parentSpanId ) . toBe ( sampledAwsSpanContext2 . spanId ) ;
1314+ } ) ;
1315+ } ) ;
0 commit comments