Skip to content

Commit 80afeac

Browse files
committed
feat(otel): Add X-Ray e2e integration tests for span validation
Add example Lambda handlers (OtelXRayStepExample, OtelXRayWaitExample) and OtelXRayIntegrationTest that validates X-Ray trace propagation end-to-end against deployed functions. Includes SAM template additions with ADOT layer config using FindInMap for architecture lookup, collector config, and CI workflow update.
1 parent da837f8 commit 80afeac

8 files changed

Lines changed: 634 additions & 1 deletion

File tree

.github/workflows/e2e-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,5 @@ jobs:
7878
'ParameterKey=Architecture,ParameterValue=x86_64 ParameterKey=JavaVersion,ParameterValue=java${{ matrix.java }} ParameterKey=RoleArn,ParameterValue=${{ secrets.DURABLE_INTEGRATION_TEST_ROLE_ARN }}'
7979
working-directory: ./examples
8080
- name: Cloud Based Integration Tests
81-
run: mvn clean test -B -Dtest.cloud.enabled=true -Dtest=CloudBasedIntegrationTest -Dtest.function.name.suffix='-java${{ matrix.java }}-runtime'
81+
run: mvn clean test -B -Dtest.cloud.enabled=true -Dtest="CloudBasedIntegrationTest,OtelXRayIntegrationTest" -Dtest.function.name.suffix='-java${{ matrix.java }}-runtime'
8282
working-directory: ./examples

examples/pom.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@
5050
<version>1.63.0</version>
5151
</dependency>
5252

53+
<!-- OTLP gRPC exporter (sends spans to ADOT collector layer at localhost:4317) -->
54+
<dependency>
55+
<groupId>io.opentelemetry</groupId>
56+
<artifactId>opentelemetry-exporter-otlp</artifactId>
57+
<version>1.63.0</version>
58+
</dependency>
59+
<dependency>
60+
<groupId>io.grpc</groupId>
61+
<artifactId>grpc-netty-shaded</artifactId>
62+
<version>1.72.0</version>
63+
</dependency>
64+
5365
<!-- AWS Lambda Java Core -->
5466
<dependency>
5567
<groupId>com.amazonaws</groupId>
@@ -91,6 +103,15 @@
91103
<artifactId>sts</artifactId>
92104
<scope>test</scope>
93105
</dependency>
106+
<dependency>
107+
<groupId>software.amazon.awssdk</groupId>
108+
<artifactId>xray</artifactId>
109+
<scope>test</scope>
110+
</dependency>
111+
<dependency>
112+
<groupId>com.fasterxml.jackson.core</groupId>
113+
<artifactId>jackson-databind</artifactId>
114+
</dependency>
94115
<dependency>
95116
<groupId>org.junit.jupiter</groupId>
96117
<artifactId>junit-jupiter</artifactId>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.examples.otel;
4+
5+
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
6+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
7+
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
8+
import software.amazon.lambda.durable.DurableConfig;
9+
import software.amazon.lambda.durable.DurableContext;
10+
import software.amazon.lambda.durable.DurableHandler;
11+
import software.amazon.lambda.durable.examples.types.GreetingRequest;
12+
import software.amazon.lambda.durable.otel.OpenTelemetryDurablePlugin;
13+
14+
/**
15+
* OTel + X-Ray example: simple steps in a single invocation.
16+
*
17+
* <p>Exports spans via OTLP to the ADOT Lambda Layer collector, which forwards them to X-Ray. Requires:
18+
*
19+
* <ul>
20+
* <li>{@code Tracing: Active} on the Lambda function
21+
* <li>ADOT Lambda Layer added to the function
22+
* <li>{@code AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler} environment variable
23+
* </ul>
24+
*
25+
* <p>Expected trace structure in X-Ray:
26+
*
27+
* <pre>
28+
* durable.invocation
29+
* ├── durable.step:create-greeting
30+
* │ └── durable.step:create-greeting [attempt 1]
31+
* └── durable.step:transform
32+
* └── durable.step:transform [attempt 1]
33+
* </pre>
34+
*/
35+
public class OtelXRayStepExample extends DurableHandler<GreetingRequest, String> {
36+
37+
@Override
38+
protected DurableConfig createConfiguration() {
39+
// OTLP exporter sends spans to the ADOT collector (localhost:4317 by default)
40+
var otlpExporter = OtlpGrpcSpanExporter.getDefault();
41+
42+
var otelPlugin = new OpenTelemetryDurablePlugin(
43+
SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(otlpExporter)));
44+
45+
return DurableConfig.builder().withPlugins(otelPlugin).build();
46+
}
47+
48+
@Override
49+
public String handleRequest(GreetingRequest input, DurableContext context) {
50+
context.getLogger().info("Starting OTel X-Ray step example for {}", input.getName());
51+
52+
var greeting = context.step("create-greeting", String.class, stepCtx -> "Hello, " + input.getName());
53+
54+
var result = context.step("transform", String.class, stepCtx -> greeting.toUpperCase() + "!");
55+
56+
context.getLogger().info("OTel X-Ray step example complete: {}", result);
57+
return result;
58+
}
59+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.examples.otel;
4+
5+
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
6+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
7+
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
8+
import java.time.Duration;
9+
import software.amazon.lambda.durable.DurableConfig;
10+
import software.amazon.lambda.durable.DurableContext;
11+
import software.amazon.lambda.durable.DurableHandler;
12+
import software.amazon.lambda.durable.examples.types.GreetingRequest;
13+
import software.amazon.lambda.durable.otel.OpenTelemetryDurablePlugin;
14+
15+
/**
16+
* OTel + X-Ray example: step → wait → step pattern that forces multiple Lambda invocations.
17+
*
18+
* <p>This handler exercises the critical multi-invocation tracing scenario:
19+
*
20+
* <ol>
21+
* <li>Invocation 1: "before-wait" step completes → wait suspends execution
22+
* <li>Invocation 2: replays "before-wait" (no-op) → wait completes → "after-wait" step runs
23+
* </ol>
24+
*
25+
* <p>Exports spans via OTLP to the ADOT Lambda Layer collector. Requires:
26+
*
27+
* <ul>
28+
* <li>{@code Tracing: Active} on the Lambda function
29+
* <li>ADOT Lambda Layer added to the function
30+
* <li>{@code AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler} environment variable
31+
* </ul>
32+
*
33+
* <p>Expected trace structure in X-Ray (all under one trace ID — backend propagates same Root):
34+
*
35+
* <pre>
36+
* Trace (single trace ID across both invocations)
37+
* ├── durable.invocation (invocation 1)
38+
* │ ├── durable.step:before-wait
39+
* │ │ └── durable.step:before-wait [attempt 1]
40+
* │ └── durable.wait:pause (ended as PENDING)
41+
* └── durable.invocation (invocation 2)
42+
* ├── durable.wait:pause (completed)
43+
* └── durable.step:after-wait
44+
* └── durable.step:after-wait [attempt 1]
45+
* </pre>
46+
*/
47+
public class OtelXRayWaitExample extends DurableHandler<GreetingRequest, String> {
48+
49+
@Override
50+
protected DurableConfig createConfiguration() {
51+
// OTLP exporter sends spans to the ADOT collector (localhost:4317 by default)
52+
var otlpExporter = OtlpGrpcSpanExporter.getDefault();
53+
54+
var otelPlugin = new OpenTelemetryDurablePlugin(
55+
SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(otlpExporter)));
56+
57+
return DurableConfig.builder().withPlugins(otelPlugin).build();
58+
}
59+
60+
@Override
61+
public String handleRequest(GreetingRequest input, DurableContext context) {
62+
context.getLogger().info("Starting OTel X-Ray wait example for {}", input.getName());
63+
64+
var before = context.step("before-wait", String.class, stepCtx -> "Prepared: " + input.getName());
65+
66+
// This wait forces Lambda to suspend and re-invoke after the duration
67+
context.wait("pause", Duration.ofSeconds(5));
68+
69+
var after = context.step("after-wait", String.class, stepCtx -> before + " | Resumed and completed");
70+
71+
context.getLogger().info("OTel X-Ray wait example complete: {}", after);
72+
return after;
73+
}
74+
}

0 commit comments

Comments
 (0)