Skip to content

Commit d3e5a78

Browse files
fix: different-x-ray-trace-ids-for-incoming-http-api-requests (#40)
1 parent 378683f commit d3e5a78

4 files changed

Lines changed: 82 additions & 15 deletions

File tree

integration-tests/iac/lib/python-tracing-scenarios-stack.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as kinesis from 'aws-cdk-lib/aws-kinesis';
1010
import * as s3 from 'aws-cdk-lib/aws-s3';
1111
import * as s3n from 'aws-cdk-lib/aws-s3-notifications';
1212
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
13+
import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
1314
import * as events from 'aws-cdk-lib/aws-events';
1415
import * as events_targets from 'aws-cdk-lib/aws-events-targets';
1516
import * as lambda_event_sources from 'aws-cdk-lib/aws-lambda-event-sources';
@@ -298,6 +299,65 @@ export class PythonTracingScenariosStack extends cdk.NestedStack {
298299
},
299300
});
300301

302+
// Scenario 6b: Lambda > HTTP API Gateway > Lambda
303+
const httpApiConsumer = new lambda.Function(this, `HttpApiConsumerLambda-${runtimeName}`, {
304+
functionName: `${prefix}tracing-httpapi-consumer-${runtimeName}`,
305+
runtime,
306+
handler: 'consumer.handler',
307+
code: pythonCode,
308+
layers: [props.layer],
309+
role,
310+
timeout: cdk.Duration.seconds(10),
311+
logGroup: props.logGroup,
312+
environment: baseEnvironment,
313+
});
314+
315+
const httpApi = new apigatewayv2.CfnApi(this, `TracingTestHttpApi-${runtimeName}`, {
316+
name: `${prefix}tracing-test-http-api-${runtimeName}`,
317+
protocolType: 'HTTP',
318+
});
319+
320+
const httpApiIntegration = new apigatewayv2.CfnIntegration(this, `TracingTestHttpApiIntegration-${runtimeName}`, {
321+
apiId: httpApi.ref,
322+
integrationType: 'AWS_PROXY',
323+
integrationUri: httpApiConsumer.functionArn,
324+
payloadFormatVersion: '2.0',
325+
});
326+
327+
new apigatewayv2.CfnRoute(this, `TracingTestHttpApiRoute-${runtimeName}`, {
328+
apiId: httpApi.ref,
329+
routeKey: 'POST /',
330+
target: `integrations/${httpApiIntegration.ref}`,
331+
});
332+
333+
new apigatewayv2.CfnStage(this, `TracingTestHttpApiStage-${runtimeName}`, {
334+
apiId: httpApi.ref,
335+
stageName: '$default',
336+
autoDeploy: true,
337+
});
338+
339+
httpApiConsumer.addPermission(`HttpApiInvokePermission-${runtimeName}`, {
340+
principal: new iam.ServicePrincipal('apigateway.amazonaws.com'),
341+
sourceArn: `arn:aws:execute-api:${this.region}:${this.account}:${httpApi.ref}/*/*`,
342+
});
343+
344+
const httpApiUrl = `https://${httpApi.ref}.execute-api.${this.region}.amazonaws.com/`;
345+
346+
const httpApiProducer = new lambda.Function(this, `HttpApiProducerLambda-${runtimeName}`, {
347+
functionName: `${prefix}tracing-httpapi-producer-${runtimeName}`,
348+
runtime,
349+
handler: 'apigateway_producer.handler',
350+
code: pythonCode,
351+
layers: [props.layer],
352+
role,
353+
timeout: cdk.Duration.seconds(10),
354+
logGroup: props.logGroup,
355+
environment: {
356+
...baseEnvironment,
357+
API_URL: httpApiUrl,
358+
},
359+
});
360+
301361
// Scenario 7: Lambda > S3 > Lambda
302362
const s3Bucket = new s3.Bucket(this, `TracingTestS3Bucket-${runtimeName}`, {
303363
bucketName: `${prefix}tracing-test-s3-bucket-${runtimeName}`,

integration-tests/tests/src/test-tracing-scenarios-general.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const nodeRuntimes = ['nodejs20-x', 'nodejs22-x', 'nodejs24-x'];
1111
const scenarios = [
1212
{ name: 'eventbridge', producerPrefix: `${RESOURCE_PREFIX}tracing-eventbridge-producer`, consumerPrefix: `${RESOURCE_PREFIX}tracing-eventbridge-consumer`, runtimes: [...pythonRuntimes, ...nodeRuntimes] },
1313
{ name: 'apigateway', producerPrefix: `${RESOURCE_PREFIX}tracing-apigateway-producer`, consumerPrefix: `${RESOURCE_PREFIX}tracing-apigateway-consumer`, runtimes: [...pythonRuntimes, ...nodeRuntimes] },
14+
{ name: 'httpapi', producerPrefix: `${RESOURCE_PREFIX}tracing-httpapi-producer`, consumerPrefix: `${RESOURCE_PREFIX}tracing-httpapi-consumer`, runtimes: [...pythonRuntimes] },
1415
{ name: 's3', producerPrefix: `${RESOURCE_PREFIX}tracing-s3-producer`, consumerPrefix: `${RESOURCE_PREFIX}tracing-s3-consumer`, runtimes: [...pythonRuntimes, ...nodeRuntimes] },
1516
{ name: 'lambda', producerPrefix: `${RESOURCE_PREFIX}tracing-lambda-invoker`, consumerPrefix: `${RESOURCE_PREFIX}tracing-lambda-consumer`, runtimes: [...pythonRuntimes, ...nodeRuntimes] },
1617
] as const;

src/otlp/span_creation.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,12 @@ fn create_root_span(
4848
let span_id = hex::decode(root_span_id).ok()?;
4949
let trace_id = hex::decode(trace_id_hex).ok()?;
5050

51-
let parent_span_id = if data.sampled || !crate::config::user::is_remove_lambda_parent_span() {
52-
data.parent_span_id
53-
.as_deref()
54-
.filter(|s| !s.is_empty())
55-
.and_then(|p| hex::decode(p).ok())
56-
.unwrap_or_default()
57-
} else {
58-
Vec::new()
59-
};
51+
let parent_span_id = data
52+
.parent_span_id
53+
.as_deref()
54+
.filter(|s| !s.is_empty())
55+
.and_then(|p| hex::decode(p).ok())
56+
.unwrap_or_default();
6057

6158
let start_nanos = ((data.start_time - data.init_duration) * 1_000_000.0) as u64;
6259
let end_nanos = (data.end_time * 1_000_000.0) as u64;

src/otlp/span_mutations.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,12 @@ fn add_event_payload_to_span(span: &mut Span, invocation_id: &str) {
455455
}
456456

457457
fn reparent_to_root_span(span: &mut Span, invocation_id: &str) {
458+
tracing::info!(
459+
"[{}] reparenting span for invocation_id={}: original parent_span_id={}",
460+
crate::log_prefix(),
461+
invocation_id,
462+
hex::encode(&span.parent_span_id),
463+
);
458464
let root_span_id = invocation_entry::get_or_create_root_span_id(invocation_id);
459465
if let Ok(bytes) = hex::decode(&root_span_id) {
460466
span.parent_span_id = bytes;
@@ -482,14 +488,17 @@ fn store_span_ids(span: &Span, invocation_id: &str) {
482488
.map(|b| format!("{:02x}", b))
483489
.collect::<String>();
484490
invocation_entry::update(invocation_id, |entry| {
491+
let is_same_trace = entry
492+
.trace_id
493+
.as_ref()
494+
.map_or(false, |existing| existing == &trace_id_hex);
485495
entry.trace_id = Some(trace_id_hex);
486496
entry.span_id = Some(span_id_hex);
497+
// we update parent span only if the lambda instrumentation extracted the context not from _X_AMZN_TRACE_ID
498+
if !span.parent_span_id.is_empty() && !is_same_trace {
499+
entry.parent_span_id = Some(hex::encode(&span.parent_span_id));
500+
}
487501
});
488-
tracing::debug!(
489-
"[{}] stored trace/span id for invocation_id={}",
490-
crate::log_prefix(),
491-
invocation_id
492-
);
493502
}
494503

495504
/// Process a decoded trace request by adding event payloads, return payloads, and storing invocation span IDs.
@@ -522,8 +531,8 @@ pub fn process_trace_request(
522531
span.name = "handler".to_string();
523532
span.kind = SpanKind::Internal as i32;
524533
add_event_payload_to_span(span, &invocation_id);
525-
reparent_to_root_span(span, &invocation_id);
526534
store_span_ids(span, &invocation_id);
535+
reparent_to_root_span(span, &invocation_id);
527536
store_handler_span_data(span, &invocation_id);
528537
}
529538
}

0 commit comments

Comments
 (0)