Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@ jobs:
'ParameterKey=Architecture,ParameterValue=x86_64 ParameterKey=JavaVersion,ParameterValue=java${{ matrix.java }} ParameterKey=RoleArn,ParameterValue=${{ secrets.DURABLE_INTEGRATION_TEST_ROLE_ARN }}'
working-directory: ./examples
- name: Cloud Based Integration Tests
run: mvn clean test -B -Dtest.cloud.enabled=true -Dtest=CloudBasedIntegrationTest -Dtest.function.name.suffix='-java${{ matrix.java }}-runtime'
run: mvn clean test -B -Dtest.cloud.enabled=true -Dtest="CloudBasedIntegrationTest,OtelXRayIntegrationTest" -Dtest.function.name.suffix='-java${{ matrix.java }}-runtime'
working-directory: ./examples
21 changes: 21 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@
<version>1.63.0</version>
</dependency>

<!-- OTLP gRPC exporter (sends spans to ADOT collector layer at localhost:4317) -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.63.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.72.0</version>
</dependency>

<!-- AWS Lambda Java Core -->
<dependency>
<groupId>com.amazonaws</groupId>
Expand Down Expand Up @@ -91,6 +103,15 @@
<artifactId>sts</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>xray</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.examples.otel;

import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import software.amazon.lambda.durable.DurableConfig;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
import software.amazon.lambda.durable.examples.types.GreetingRequest;
import software.amazon.lambda.durable.otel.OpenTelemetryDurablePlugin;

/**
* OTel + X-Ray example: simple steps in a single invocation.
*
* <p>Exports spans via OTLP to the ADOT Lambda Layer collector, which forwards them to X-Ray. Requires:
*
* <ul>
* <li>{@code Tracing: Active} on the Lambda function
* <li>ADOT Lambda Layer added to the function
* <li>{@code AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler} environment variable
* </ul>
*
* <p>Expected trace structure in X-Ray:
*
* <pre>
* durable.invocation
* ├── durable.step:create-greeting
* │ └── durable.step:create-greeting [attempt 1]
* └── durable.step:transform
* └── durable.step:transform [attempt 1]
* </pre>
*/
public class OtelXRayStepExample extends DurableHandler<GreetingRequest, String> {

@Override
protected DurableConfig createConfiguration() {
// OTLP exporter sends spans to the ADOT collector (localhost:4317 by default)
var otlpExporter = OtlpGrpcSpanExporter.getDefault();

var otelPlugin = new OpenTelemetryDurablePlugin(
SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(otlpExporter)));

return DurableConfig.builder().withPlugins(otelPlugin).build();
}

@Override
public String handleRequest(GreetingRequest input, DurableContext context) {
context.getLogger().info("Starting OTel X-Ray step example for {}", input.getName());

var greeting = context.step("create-greeting", String.class, stepCtx -> "Hello, " + input.getName());

var result = context.step("transform", String.class, stepCtx -> greeting.toUpperCase() + "!");

context.getLogger().info("OTel X-Ray step example complete: {}", result);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.examples.otel;

import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import java.time.Duration;
import software.amazon.lambda.durable.DurableConfig;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
import software.amazon.lambda.durable.examples.types.GreetingRequest;
import software.amazon.lambda.durable.otel.OpenTelemetryDurablePlugin;

/**
* OTel + X-Ray example: step → wait → step pattern that forces multiple Lambda invocations.
*
* <p>This handler exercises the critical multi-invocation tracing scenario:
*
* <ol>
* <li>Invocation 1: "before-wait" step completes → wait suspends execution
* <li>Invocation 2: replays "before-wait" (no-op) → wait completes → "after-wait" step runs
* </ol>
*
* <p>Exports spans via OTLP to the ADOT Lambda Layer collector. Requires:
*
* <ul>
* <li>{@code Tracing: Active} on the Lambda function
* <li>ADOT Lambda Layer added to the function
* <li>{@code AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler} environment variable
* </ul>
*
* <p>Expected trace structure in X-Ray (all under one trace ID — backend propagates same Root):
*
* <pre>
* Trace (single trace ID across both invocations)
* ├── durable.invocation (invocation 1)
* │ ├── durable.step:before-wait
* │ │ └── durable.step:before-wait [attempt 1]
* │ └── durable.wait:pause (ended as PENDING)
* └── durable.invocation (invocation 2)
* ├── durable.wait:pause (completed)
* └── durable.step:after-wait
* └── durable.step:after-wait [attempt 1]
* </pre>
*/
public class OtelXRayWaitExample extends DurableHandler<GreetingRequest, String> {

@Override
protected DurableConfig createConfiguration() {
// OTLP exporter sends spans to the ADOT collector (localhost:4317 by default)
var otlpExporter = OtlpGrpcSpanExporter.getDefault();

var otelPlugin = new OpenTelemetryDurablePlugin(
SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(otlpExporter)));

return DurableConfig.builder().withPlugins(otelPlugin).build();
}

@Override
public String handleRequest(GreetingRequest input, DurableContext context) {
context.getLogger().info("Starting OTel X-Ray wait example for {}", input.getName());

var before = context.step("before-wait", String.class, stepCtx -> "Prepared: " + input.getName());

// This wait forces Lambda to suspend and re-invoke after the duration
context.wait("pause", Duration.ofSeconds(5));

var after = context.step("after-wait", String.class, stepCtx -> before + " | Resumed and completed");

context.getLogger().info("OTel X-Ray wait example complete: {}", after);
return after;
}
}
Loading