Skip to content
Draft
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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ sdk-integration-tests/src/test/ # SDK components working together via LocalDura
├── MapIntegrationTest
├── ParallelIntegrationTest
├── WaitForConditionIntegrationTest
└── OtelPluginIntegrationTest
└── InvocationOtelPluginIntegrationTest

otel-plugin/src/test/ # OpenTelemetry plugin unit tests

Expand Down Expand Up @@ -272,7 +272,7 @@ void testAgainstRealLambda() {
| `ParallelOperation` | Runs named child-context branches concurrently |
| `ConcurrencyOperation` | Shared base for map/parallel concurrency limiting and completion evaluation |
| `DurableExecutionPlugin` | Preview lifecycle hook interface |
| `OtelPlugin` | OpenTelemetry plugin implementation in `otel-plugin` |
| `InvocationOtelPlugin` | OpenTelemetry plugin implementation in `otel-plugin` |

### Serialization

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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.OtelPlugin;
import software.amazon.lambda.durable.otel.InvocationOtelPlugin;

/**
* Example demonstrating OpenTelemetry instrumentation with the Durable Execution SDK.
Expand Down Expand Up @@ -39,7 +39,7 @@ public class OtelExample extends DurableHandler<GreetingRequest, String> {

@Override
protected DurableConfig createConfiguration() {
var otelPlugin = new OtelPlugin(
var otelPlugin = new InvocationOtelPlugin(
SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())));

return DurableConfig.builder().withPlugins(otelPlugin).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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.OtelPlugin;
import software.amazon.lambda.durable.otel.InvocationOtelPlugin;

/**
* OTel examples for map, parallel, and nested context operations. These are local-only examples (not deployed to
Expand All @@ -22,8 +22,8 @@ private OtelXRayExamples() {}

private static DurableConfig otelConfig() {
var otlpExporter = OtlpGrpcSpanExporter.getDefault();
var otelPlugin =
new OtelPlugin(SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(otlpExporter)));
var otelPlugin = new InvocationOtelPlugin(
SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(otlpExporter)));
return DurableConfig.builder().withPlugins(otelPlugin).build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import software.amazon.lambda.durable.DurableHandler;
import software.amazon.lambda.durable.examples.ExampleTemplate;
import software.amazon.lambda.durable.examples.types.GreetingRequest;
import software.amazon.lambda.durable.otel.OtelPlugin;
import software.amazon.lambda.durable.otel.InvocationOtelPlugin;

/**
* OTel + X-Ray example: simple steps in a single invocation.
Expand Down Expand Up @@ -40,8 +40,8 @@ protected DurableConfig createConfiguration() {
// OTLP exporter sends spans to the ADOT collector (localhost:4317 by default)
var otlpExporter = OtlpGrpcSpanExporter.getDefault();

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

return DurableConfig.builder().withPlugins(otelPlugin).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import software.amazon.lambda.durable.DurableHandler;
import software.amazon.lambda.durable.examples.ExampleTemplate;
import software.amazon.lambda.durable.examples.types.GreetingRequest;
import software.amazon.lambda.durable.otel.OtelPlugin;
import software.amazon.lambda.durable.otel.InvocationOtelPlugin;

/**
* OTel + X-Ray example: step → wait → step pattern that forces multiple Lambda invocations.
Expand Down Expand Up @@ -52,8 +52,8 @@ protected DurableConfig createConfiguration() {
// OTLP exporter sends spans to the ADOT collector (localhost:4317 by default)
var otlpExporter = OtlpGrpcSpanExporter.getDefault();

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

return DurableConfig.builder().withPlugins(otelPlugin).build();
}
Expand Down
14 changes: 7 additions & 7 deletions otel-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ You also need the OpenTelemetry SDK and an exporter:

1. Add the ADOT Lambda Layer to your function
2. Enable X-Ray Active Tracing on the function
3. Register `OtelPlugin` in your handler's `DurableConfig`
3. Register `InvocationOtelPlugin` in your handler's `DurableConfig`
4. Grant X-Ray write permissions

### 1. ADOT Lambda Layer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the README in a separate PR

Expand Down Expand Up @@ -110,15 +110,15 @@ 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.otel.OtelPlugin;
import software.amazon.lambda.durable.otel.InvocationOtelPlugin;

public class MyHandler extends DurableHandler<MyInput, MyOutput> {

@Override
protected DurableConfig createConfiguration() {
var otlpExporter = OtlpGrpcSpanExporter.getDefault();

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

Expand Down Expand Up @@ -215,13 +215,13 @@ These appear automatically in structured log output (Log4j2 JSON, Logback JSON)

```java
// Default: X-Ray context extraction, MDC enabled
new OtelPlugin(tracerProviderBuilder);
new InvocationOtelPlugin(tracerProviderBuilder);

// Custom context extractor, MDC enabled
new OtelPlugin(tracerProviderBuilder, contextExtractor);
new InvocationOtelPlugin(tracerProviderBuilder, contextExtractor);

// Full configuration
new OtelPlugin(tracerProviderBuilder, contextExtractor, enableMdc);
new InvocationOtelPlugin(tracerProviderBuilder, contextExtractor, enableMdc);
```

| Parameter | Description | Default |
Expand Down Expand Up @@ -262,7 +262,7 @@ For local testing, use a logging exporter to print spans to stdout:
```java
import io.opentelemetry.exporter.logging.LoggingSpanExporter;

var otelPlugin = new OtelPlugin(
var otelPlugin = new InvocationOtelPlugin(
SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())));
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class DeterministicIdGenerator implements IdGenerator {
private final AtomicReference<String> extractedTraceId = new AtomicReference<>(null);
private final AtomicReference<String> arnDerivedTraceId = new AtomicReference<>(null);
private final ThreadLocal<String> pendingSpanOperationId = new ThreadLocal<>();
private final ThreadLocal<String> pendingRawSpanId = new ThreadLocal<>();
private final AtomicReference<String> durableExecutionArn = new AtomicReference<>(null);

/**
Expand Down Expand Up @@ -66,6 +67,17 @@ public void setNextSpanOperationId(String operationId) {
this.pendingSpanOperationId.set(operationId);
}

/**
* Queues the next span to use the given pre-computed span ID verbatim. Unlike {@link #setNextSpanOperationId}, the
* supplied value is used directly rather than derived from an operation ID. Used for the Workflow root span, whose
* ID is derived once from the execution ARN via {@link #generateWorkflowSpanId()}.
*
* @param spanId a 16-char lowercase hex span ID
*/
public void setNextSpanId(String spanId) {
this.pendingRawSpanId.set(spanId);
}

/**
* Generates a deterministic span ID for a given operation ID without consuming the ThreadLocal state.
*
Expand All @@ -76,6 +88,24 @@ public String generateSpanIdForOperation(String operationId) {
return generateSpanIdFromOperation(operationId);
}

/**
* Generates the deterministic span ID for the Workflow root span from the current execution ARN, using the seed
* {@code "workflow:" + arn} (SHA-256, truncated to 16 hex chars). Stable across all invocations of the same
* execution so the Workflow span is exported once as a single logical span. Guarded to never be all-zero (an
* invalid OTel span ID).
*
* @return a deterministic 16-char hex span ID
*/
public String generateWorkflowSpanId() {
var arn = durableExecutionArn.get();
var seed = "workflow:" + (arn != null ? arn : "");
var spanId = sha256(seed).substring(0, 16);
if (spanId.equals("0000000000000000")) {
spanId = "0000000000000001";
}
return spanId;
}

@Override
public String generateTraceId() {
// Priority 1: extracted from X-Ray header (backend propagates same Root across invocations)
Expand All @@ -94,6 +124,11 @@ public String generateTraceId() {

@Override
public String generateSpanId() {
var raw = pendingRawSpanId.get();
if (raw != null) {
pendingRawSpanId.remove();
return raw;
}
var operationId = pendingSpanOperationId.get();
if (operationId != null) {
pendingSpanOperationId.remove();
Expand Down
Loading