Skip to content

Commit ef05b7f

Browse files
authored
Support Trace Context extraction from Lambda Context object, and respect user-configured OTEL_PROPAGATORS (#259)
*Issue #, if available:* - Alternative solution to #258 - Also change customExtractor to be more similar to [AWS Lambda Instrumentation's default Extractor](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/instrumentation-aws-lambda-v0.54.1/packages/instrumentation-aws-lambda/src/instrumentation.ts#L426-L430), in order to respect `OTEL_PROPAGATORS` Env Var. *Description of changes:* By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 93116db commit ef05b7f

File tree

5 files changed

+316
-69
lines changed

5 files changed

+316
-69
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ For any change that affects end users of this package, please add an entry under
1212
If your change does not need a CHANGELOG entry, add the "skip changelog" label to your PR.
1313

1414
## Unreleased
15+
16+
### Enhancements
17+
18+
- Support X-Ray Trace Id extraction from Lambda Context object, and respect user-configured OTEL_PROPAGATORS in AWS Lamdba instrumentation
19+
([#259](https://github.com/aws-observability/aws-otel-js-instrumentation/pull/259))

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

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import {
55
diag,
6-
isSpanContextValid,
76
Context as OtelContext,
87
context as otelContext,
98
propagation,
@@ -22,7 +21,7 @@ import {
2221
NormalizedRequest,
2322
NormalizedResponse,
2423
} from '@opentelemetry/instrumentation-aws-sdk';
25-
import { AWSXRAY_TRACE_ID_HEADER, AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray';
24+
import { AWSXRAY_TRACE_ID_HEADER } from '@opentelemetry/propagator-aws-xray';
2625
import { APIGatewayProxyEventHeaders, Context } from 'aws-lambda';
2726
import { AWS_ATTRIBUTE_KEYS } from '../aws-attribute-keys';
2827
import { RequestMetadata } from '../third-party/otel/aws/services/ServiceExtension';
@@ -42,7 +41,6 @@ import { suppressTracing } from '@opentelemetry/core';
4241
export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID';
4342
export const AWSXRAY_TRACE_ID_HEADER_CAPITALIZED = 'X-Amzn-Trace-Id';
4443

45-
const awsPropagator = new AWSXRayPropagator();
4644
export const headerGetter: TextMapGetter<APIGatewayProxyEventHeaders> = {
4745
keys(carrier: any): string[] {
4846
return Object.keys(carrier);
@@ -94,28 +92,29 @@ export function applyInstrumentationPatches(instrumentations: Instrumentation[])
9492

9593
/*
9694
* This function `customExtractor` is used to extract SpanContext for AWS Lambda functions.
97-
* It first attempts to extract the trace context from the AWS X-Ray header, which is stored in the Lambda environment variables.
98-
* If a valid span context is extracted from the environment, it uses this as the parent context for the function's tracing.
99-
* If the X-Ray header is missing or invalid, it falls back to extracting trace context from the Lambda handler's event headers.
100-
* If neither approach succeeds, it defaults to using the root Otel context, ensuring the function is still instrumented for tracing.
95+
* It extracts the X-Ray trace ID from the Lambda context (xRayTraceId) or environment variable (_X_AMZN_TRACE_ID) into the event headers
96+
* It then uses OpenTelemetry global propagator to extract trace context from the event headers
97+
* If a valid span context is extracted, it returns that context; otherwise, it returns the root context.
10198
*/
99+
const lambdaContextXrayTraceIdKey = 'xRayTraceId';
102100
export const customExtractor = (event: any, _handlerContext: Context): OtelContext => {
103-
let parent: OtelContext | undefined = undefined;
104-
const lambdaTraceHeader = process.env[traceContextEnvironmentKey];
105-
if (lambdaTraceHeader) {
106-
parent = awsPropagator.extract(
107-
otelContext.active(),
108-
{ [AWSXRAY_TRACE_ID_HEADER]: lambdaTraceHeader },
109-
headerGetter
110-
);
111-
}
112-
if (parent) {
113-
const spanContext = trace.getSpan(parent)?.spanContext();
114-
if (spanContext && isSpanContextValid(spanContext)) {
115-
return parent;
116-
}
117-
}
101+
const xrayTraceIdFromLambdaContext = _handlerContext
102+
? (_handlerContext as any)[lambdaContextXrayTraceIdKey]
103+
: undefined;
104+
const xrayTraceIdFromLambdaEnv = process.env[traceContextEnvironmentKey];
105+
const xrayTraceIdFromLambda = xrayTraceIdFromLambdaContext || xrayTraceIdFromLambdaEnv;
106+
118107
const httpHeaders = event.headers || {};
108+
if (xrayTraceIdFromLambda) {
109+
// Delete any X-Ray Trace ID via case-insensitive checks since we will overwrite it here.
110+
Object.keys(httpHeaders).forEach(key => {
111+
if (key.toLowerCase() === AWSXRAY_TRACE_ID_HEADER.toLowerCase()) {
112+
delete httpHeaders[key];
113+
}
114+
});
115+
httpHeaders[AWSXRAY_TRACE_ID_HEADER] = xrayTraceIdFromLambda;
116+
}
117+
119118
const extractedContext = propagation.extract(otelContext.active(), httpHeaders, headerGetter);
120119
if (trace.getSpan(extractedContext)?.spanContext()) {
121120
return extractedContext;

0 commit comments

Comments
 (0)