Skip to content

Commit 21887a0

Browse files
committed
update otel propagator ordering in lambda to prioritize xray header
1 parent 7a8ca7a commit 21887a0

File tree

3 files changed

+165
-19
lines changed

3 files changed

+165
-19
lines changed

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

Lines changed: 156 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ import { instrumentationConfigs } from '../../src/register';
4444
import { LoggerProvider } from '@opentelemetry/api-logs';
4545
import { STS } from '@aws-sdk/client-sts';
4646
import { 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

968972
describe('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+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
const api = require('@opentelemetry/api');
5+
6+
exports.handler = async function (event, context) {
7+
return 'hello world';
8+
};

lambda-layer/packages/layer/scripts/otel-instrument

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fi
6868

6969
# - Set the propagators
7070
if [[ -z "$OTEL_PROPAGATORS" ]]; then
71-
export OTEL_PROPAGATORS="baggage,xray,tracecontext"
71+
export OTEL_PROPAGATORS="baggage,tracecontext,xray"
7272
fi
7373

7474
# - Set Application Signals configuration

0 commit comments

Comments
 (0)