Skip to content

Commit 56534df

Browse files
feat(sdk): Add DurableInstrumentationPlugin interface and plugin runner (#408)
1 parent 8fdfda8 commit 56534df

13 files changed

Lines changed: 1037 additions & 0 deletions

sdk/src/main/java/software/amazon/lambda/durable/DurableConfig.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import software.amazon.lambda.durable.client.DurableExecutionClient;
2222
import software.amazon.lambda.durable.client.LambdaDurableFunctionsClient;
2323
import software.amazon.lambda.durable.logging.LoggerConfig;
24+
import software.amazon.lambda.durable.plugin.PluginRunner;
2425
import software.amazon.lambda.durable.retry.PollingStrategies;
2526
import software.amazon.lambda.durable.retry.PollingStrategy;
2627
import software.amazon.lambda.durable.serde.JacksonSerDes;
@@ -94,6 +95,7 @@ public final class DurableConfig {
9495
private final LoggerConfig loggerConfig;
9596
private final PollingStrategy pollingStrategy;
9697
private final Duration checkpointDelay;
98+
private final PluginRunner pluginRunner;
9799

98100
private DurableConfig(Builder builder) {
99101
this.durableExecutionClient = Objects.requireNonNullElseGet(
@@ -104,6 +106,7 @@ private DurableConfig(Builder builder) {
104106
this.loggerConfig = Objects.requireNonNullElseGet(builder.loggerConfig, LoggerConfig::defaults);
105107
this.pollingStrategy = Objects.requireNonNullElse(builder.pollingStrategy, PollingStrategies.Presets.DEFAULT);
106108
this.checkpointDelay = Objects.requireNonNullElseGet(builder.checkpointDelay, () -> Duration.ofSeconds(0));
109+
this.pluginRunner = PluginRunner.noOp();
107110

108111
validateConfiguration();
109112
}
@@ -180,6 +183,20 @@ public Duration getCheckpointDelay() {
180183
return checkpointDelay;
181184
}
182185

186+
/**
187+
* Gets the plugin runner that dispatches lifecycle events to registered plugins.
188+
*
189+
* <p>Currently returns a no-op runner. Plugin registration via config will be added when the plugin system is fully
190+
* wired.
191+
*
192+
* @return PluginRunner instance (always no-op until plugin wiring is complete)
193+
* @deprecated This is a preview API that is experimental and may be changed or removed in future releases.
194+
*/
195+
@Deprecated
196+
public PluginRunner getPluginRunner() {
197+
return pluginRunner;
198+
}
199+
183200
public void validateConfiguration() {
184201
if (getDurableExecutionClient() == null) {
185202
throw new IllegalStateException("DurableExecutionClient configuration failed");
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.plugin;
4+
5+
/**
6+
* Plugin interface for instrumenting durable execution lifecycle events.
7+
*
8+
* <p>Implement this interface to integrate observability tools (OpenTelemetry, Datadog, etc.) with the durable
9+
* execution SDK. The SDK calls these hooks at key lifecycle points without requiring modifications to core SDK code.
10+
*
11+
* <p>All methods have default no-op implementations, allowing plugins to override only the hooks they need.
12+
*
13+
* <p>Plugin errors are isolated — exceptions thrown by plugin methods are caught and logged but never disrupt SDK
14+
* execution.
15+
*
16+
* @deprecated This is a preview API that is experimental and may be changed or removed in future releases.
17+
*/
18+
@Deprecated
19+
public interface DurableExecutionPlugin {
20+
21+
// ─── Invocation-level hooks ──────────────────────────────────────────
22+
23+
/**
24+
* Called at the start of each Lambda invocation. Use to set up per-invocation state (trace ID, invocation span).
25+
*
26+
* <p>Check {@link InvocationInfo#isFirstInvocation()} to detect the first invocation of an execution (useful for
27+
* sampling decisions or execution-level span creation).
28+
*/
29+
default void onInvocationStart(InvocationInfo info) {}
30+
31+
/**
32+
* Called at the end of each Lambda invocation. Use to flush spans/metrics before Lambda freezes.
33+
*
34+
* <p>This hook is awaited — the SDK blocks until it returns. This is the only safe flush point before Lambda
35+
* freezes the execution environment.
36+
*
37+
* <p>Check {@link InvocationEndInfo#invocationStatus()} to detect if the execution reached a terminal state in this
38+
* invocation (useful for writing summary records or flushing final data).
39+
*/
40+
default void onInvocationEnd(InvocationEndInfo info) {}
41+
42+
// ─── Operation-level hooks ───────────────────────────────────────────
43+
44+
/** Called when an operation starts (including replay). Use for logging/metrics that want replay visibility. */
45+
default void onOperationStart(OperationInfo info) {}
46+
47+
/**
48+
* Called when an operation reaches a terminal status for the first time (not on replay).
49+
*
50+
* <p>The OTel plugin creates operation spans here with backfilled start/end timestamps.
51+
*/
52+
default void onOperationEnd(OperationEndInfo info) {}
53+
54+
// ─── User function hooks ─────────────────────────────────────────────
55+
56+
/**
57+
* Called when a user-provided function starts executing. This fires for both step attempts (with {@code attempt}
58+
* set) and child context functions (with {@code attempt} null).
59+
*
60+
* <p>This hook fires on the same thread as user code, so plugins can set OTel context via
61+
* {@code Context.makeCurrent()} here.
62+
*/
63+
default void onUserFunctionStart(UserFunctionStartInfo info) {}
64+
65+
/**
66+
* Called when a user-provided function finishes executing. This fires for both step attempts and child context
67+
* functions.
68+
*
69+
* <p>This hook fires on the same thread as user code, so plugins can close OTel scopes here.
70+
*/
71+
default void onUserFunctionEnd(UserFunctionEndInfo info) {}
72+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.plugin;
4+
5+
/**
6+
* Information provided at the end of a Lambda invocation.
7+
*
8+
* @param requestId the Lambda request ID for this invocation
9+
* @param executionArn the durable execution ARN
10+
* @param isFirstInvocation true if this is the first invocation of the execution
11+
* @param invocationStatus the invocation outcome (SUCCEEDED, FAILED, or PENDING)
12+
* @param executionError non-null if the execution failed
13+
* @deprecated This is a preview API that is experimental and may be changed or removed in future releases.
14+
*/
15+
@Deprecated
16+
public record InvocationEndInfo(
17+
String requestId,
18+
String executionArn,
19+
boolean isFirstInvocation,
20+
InvocationStatus invocationStatus,
21+
Throwable executionError) {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.plugin;
4+
5+
/**
6+
* Invocation-level information available to plugin hooks.
7+
*
8+
* @param requestId the Lambda request ID for this invocation
9+
* @param executionArn the durable execution ARN
10+
* @param isFirstInvocation true if this is the first invocation of the execution (not a replay invocation)
11+
* @deprecated This is a preview API that is experimental and may be changed or removed in future releases.
12+
*/
13+
@Deprecated
14+
public record InvocationInfo(String requestId, String executionArn, boolean isFirstInvocation) {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.plugin;
4+
5+
/**
6+
* Status of a Lambda invocation at the end of its execution.
7+
*
8+
* @deprecated This is a preview API that is experimental and may be changed or removed in future releases.
9+
*/
10+
@Deprecated
11+
public enum InvocationStatus {
12+
/** Execution completed successfully in this invocation. */
13+
SUCCEEDED,
14+
/** Execution failed in this invocation. */
15+
FAILED,
16+
/** Execution suspended — will resume in a future invocation. */
17+
PENDING
18+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.plugin;
4+
5+
import java.time.Instant;
6+
7+
/**
8+
* Extended operation information for operation end events.
9+
*
10+
* @param id operation ID — hashed
11+
* @param rawId operation ID — unhashed
12+
* @param name human-readable operation name (may be null)
13+
* @param type operation type
14+
* @param subType operation sub-type (may be null)
15+
* @param parentId parent operation ID — hashed (null for root-level operations)
16+
* @param rawParentId parent operation ID — unhashed (null for root-level operations)
17+
* @param startTimestamp when the operation started
18+
* @param endTimestamp when the operation ended
19+
* @param error non-null if the operation failed
20+
* @deprecated This is a preview API that is experimental and may be changed or removed in future releases.
21+
*/
22+
@Deprecated
23+
public record OperationEndInfo(
24+
String id,
25+
String rawId,
26+
String name,
27+
String type,
28+
String subType,
29+
String parentId,
30+
String rawParentId,
31+
Instant startTimestamp,
32+
Instant endTimestamp,
33+
Throwable error) {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.lambda.durable.plugin;
4+
5+
import java.time.Instant;
6+
7+
/**
8+
* Operation-level information available to plugin hooks.
9+
*
10+
* <p>Field names mirror the {@code Operation} type from the AWS SDK for consistency.
11+
*
12+
* @param id operation ID — hashed (unique within the execution)
13+
* @param rawId operation ID — unhashed (e.g. "1", "2", "hash(1)-1" for child contexts)
14+
* @param name human-readable operation name (may be null)
15+
* @param type operation type (STEP, WAIT, CONTEXT, CHAINED_INVOKE, CALLBACK)
16+
* @param subType operation sub-type (Map, Parallel, RunInChildContext, WaitForCondition, etc.) — may be null
17+
* @param parentId parent operation ID — hashed (null for root-level operations)
18+
* @param rawParentId parent operation ID — unhashed (null for root-level operations)
19+
* @param startTimestamp when the operation started (may be from a prior invocation)
20+
* @param endTimestamp when the operation ended (null if still running)
21+
* @deprecated This is a preview API that is experimental and may be changed or removed in future releases.
22+
*/
23+
@Deprecated
24+
public record OperationInfo(
25+
String id,
26+
String rawId,
27+
String name,
28+
String type,
29+
String subType,
30+
String parentId,
31+
String rawParentId,
32+
Instant startTimestamp,
33+
Instant endTimestamp) {}

0 commit comments

Comments
 (0)