Skip to content

Commit 768a50e

Browse files
committed
docs: add plugins page to SDK reference
1 parent de63adf commit 768a50e

23 files changed

Lines changed: 512 additions & 0 deletions

docs/sdk-reference/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ The core building blocks for constructing durable workflows:
3030

3131
- [Logging](observability/logging.md) Structured logging within durable functions
3232

33+
## Plugins
34+
35+
- [Plugins](plugins/plugins.md) Instrument the execution lifecycle to integrate external tooling
36+
3337
## Configuration
3438

3539
- [Custom Lambda Client](configuration/custom-lambda-client.md) Configure the Lambda
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# Plugins
2+
3+
Create plugins for instrumenting durable execution lifecycle events. Each SDK
4+
calls plugin hooks at invocation, operation, and user-function boundaries, so
5+
you can integrate external tooling, such as observability platforms, without
6+
changing your handler logic. For example, you can use a plugin to emit a metric
7+
or trace span each time an operation runs. Errors thrown by a plugin are caught
8+
and logged, and never affect the execution outcome.
9+
10+
!!! note "Experimental feature"
11+
12+
The plugin interface is currently experimental and may change in a
13+
future release. Feedback is welcome, please share your input through our
14+
[GitHub Discussion](https://github.com/aws/aws-durable-execution-docs/discussions/206).
15+
16+
## Define a plugin
17+
18+
A plugin implements any of the following lifecycle hooks, and the SDK calls
19+
them as the execution runs. Every hook is optional, so implement only the
20+
ones you need.
21+
22+
The following example plugin shows how to implement every available hook.
23+
24+
=== "TypeScript"
25+
26+
```typescript
27+
--8<-- "examples/typescript/sdk-reference/plugins/complete-plugin.ts"
28+
```
29+
30+
=== "Python"
31+
32+
```python
33+
--8<-- "examples/python/sdk-reference/plugins/complete-plugin.py"
34+
```
35+
36+
=== "Java"
37+
38+
```java
39+
--8<-- "examples/java/sdk-reference/plugins/complete-plugin.java"
40+
```
41+
42+
The hooks fire at three boundaries: invocation, operation, and the user
43+
function. Each hook receives an info object describing that event, including
44+
fields like operation identity, timestamps, or error details.
45+
46+
### Invocation hooks
47+
48+
Invocation hooks run when a Lambda invocation of the execution starts or ends.
49+
The SDK passes an `isFirstInvocation` flag to the start hook, which indicates
50+
whether this is the execution's first invocation or a replay.
51+
52+
=== "TypeScript"
53+
54+
```typescript
55+
--8<-- "examples/typescript/sdk-reference/plugins/invocation-hooks.ts"
56+
```
57+
58+
`onInvocationStart` and `onInvocationEnd` both return `Promise<void>`, and
59+
the SDK awaits them before continuing the execution.
60+
61+
=== "Python"
62+
63+
```python
64+
--8<-- "examples/python/sdk-reference/plugins/invocation-hooks.py"
65+
```
66+
67+
=== "Java"
68+
69+
```java
70+
--8<-- "examples/java/sdk-reference/plugins/invocation-hooks.java"
71+
```
72+
73+
The SDK awaits `onInvocationEnd` before continuing the execution. All other
74+
hooks are fire-and-forget.
75+
76+
### Operation hooks
77+
78+
Operation hooks run when a durable operation, such as a step or wait, starts
79+
or reaches a terminal status.
80+
81+
=== "TypeScript"
82+
83+
```typescript
84+
--8<-- "examples/typescript/sdk-reference/plugins/operation-hooks.ts"
85+
```
86+
87+
The `isReplay` field on `OperationInfo` indicates whether the hook is
88+
firing for the first time or during a replay.
89+
90+
=== "Python"
91+
92+
```python
93+
--8<-- "examples/python/sdk-reference/plugins/operation-hooks.py"
94+
```
95+
96+
=== "Java"
97+
98+
```java
99+
--8<-- "examples/java/sdk-reference/plugins/operation-hooks.java"
100+
```
101+
102+
### User-function hooks
103+
104+
User-function hooks run when the code you supply for an operation begins or
105+
finishes, on that operation's thread.
106+
107+
=== "TypeScript"
108+
109+
```typescript
110+
--8<-- "examples/typescript/sdk-reference/plugins/user-function-hooks.ts"
111+
```
112+
113+
These hooks fire around each step or wait-for-condition attempt. Child
114+
context functions are instrumented through `wrapChildContextFn`, a
115+
TypeScript-only hook described in
116+
[TypeScript-only hooks](#typescript-only-hooks).
117+
118+
=== "Python"
119+
120+
```python
121+
--8<-- "examples/python/sdk-reference/plugins/user-function-hooks.py"
122+
```
123+
124+
These hooks fire around step attempts, child context functions, and
125+
wait-for-condition checks.
126+
127+
=== "Java"
128+
129+
```java
130+
--8<-- "examples/java/sdk-reference/plugins/user-function-hooks.java"
131+
```
132+
133+
These hooks fire around step attempts, child context functions, and
134+
wait-for-condition checks.
135+
136+
## Use a plugin
137+
138+
Provide plugins in the durable handler configuration. The SDK calls each plugin
139+
in the order you provide.
140+
141+
=== "TypeScript"
142+
143+
```typescript
144+
--8<-- "examples/typescript/sdk-reference/plugins/use-plugin.ts"
145+
```
146+
147+
=== "Python"
148+
149+
```python
150+
--8<-- "examples/python/sdk-reference/plugins/use-plugin.py"
151+
```
152+
153+
=== "Java"
154+
155+
```java
156+
--8<-- "examples/java/sdk-reference/plugins/use-plugin.java"
157+
```
158+
159+
## Logging from a plugin
160+
161+
A plugin can add custom fields to log entries so that logs emitted during the
162+
execution carry extra context.
163+
164+
=== "TypeScript"
165+
166+
`enrichLogContext` is called for each log entry, and the fields it returns
167+
are added to that entry. The enrichment applies wherever the SDK logs. See
168+
[Logging](../observability/logging.md) for more about the SDK logger.
169+
170+
```typescript
171+
--8<-- "examples/typescript/sdk-reference/plugins/log-enrichment-hook.ts"
172+
```
173+
174+
=== "Python"
175+
176+
A plugin can enrich logs by installing a standard `logging.Filter` on the
177+
root logger, usually in its constructor. The filter runs for every log
178+
record on the emitting thread, so it can add fields that reflect the
179+
plugin's current state. See
180+
[Logging](../observability/logging.md) for more about the SDK logger.
181+
182+
```python
183+
--8<-- "examples/python/sdk-reference/plugins/log-enrichment.py"
184+
```
185+
186+
=== "Java"
187+
188+
The `onUserFunctionStart` and `onUserFunctionEnd` hooks run on the same
189+
thread as the user function, so a plugin can add fields to the SLF4J MDC in
190+
those hooks and the logs the function emits include those fields. See
191+
[Logging](../observability/logging.md) for more information.
192+
193+
```java
194+
--8<-- "examples/java/sdk-reference/plugins/log-enrichment.java"
195+
```
196+
197+
## TypeScript-only hooks
198+
199+
!!! warning "TypeScript only"
200+
201+
These hooks exist only in the TypeScript SDK. The Python and Java plugin
202+
interfaces provide observer hooks only, and these methods will not be added
203+
to them.
204+
205+
The TypeScript SDK adds wrapper hooks and a checkpoint-change hook. These hooks
206+
were added to match the callback style common in JavaScript.
207+
208+
### Wrapper hooks
209+
210+
Wrapper hooks run code around a unit of work. `wrapInvocation` wraps the whole
211+
invocation, `wrapChildContextFn` wraps a child context function, and
212+
`wrapOperationAttemptFn` wraps a single step or wait-for-condition attempt. Use
213+
them to run setup and teardown around the work, such as establishing context
214+
that the wrapped code runs within.
215+
216+
```typescript
217+
--8<-- "examples/typescript/sdk-reference/plugins/wrapper-hooks.ts"
218+
```
219+
220+
### Checkpoint-change hook
221+
222+
`onOperationChange` fires when a checkpoint response reports that operations
223+
changed status. It receives the operations that changed and a snapshot of all
224+
operations.
225+
226+
```typescript
227+
--8<-- "examples/typescript/sdk-reference/plugins/checkpoint-change-hook.ts"
228+
```
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import software.amazon.lambda.durable.plugin.DurableExecutionPlugin;
2+
import software.amazon.lambda.durable.plugin.InvocationInfo;
3+
import software.amazon.lambda.durable.plugin.InvocationEndInfo;
4+
import software.amazon.lambda.durable.plugin.OperationInfo;
5+
import software.amazon.lambda.durable.plugin.OperationEndInfo;
6+
import software.amazon.lambda.durable.plugin.UserFunctionStartInfo;
7+
import software.amazon.lambda.durable.plugin.UserFunctionEndInfo;
8+
9+
public class ExamplePlugin implements DurableExecutionPlugin {
10+
@Override
11+
public void onInvocationStart(InvocationInfo info) {
12+
System.out.println("invocation start " + info.durableExecutionArn()
13+
+ " " + info.isFirstInvocation());
14+
}
15+
16+
@Override
17+
public void onInvocationEnd(InvocationEndInfo info) {
18+
System.out.println("invocation end " + info.invocationStatus());
19+
}
20+
21+
@Override
22+
public void onOperationStart(OperationInfo info) {
23+
System.out.println("operation start " + info.type() + " " + info.name());
24+
}
25+
26+
@Override
27+
public void onOperationEnd(OperationEndInfo info) {
28+
System.out.println("operation end " + info.name());
29+
}
30+
31+
@Override
32+
public void onUserFunctionStart(UserFunctionStartInfo info) {
33+
System.out.println("user function start " + info.name() + " " + info.attempt());
34+
}
35+
36+
@Override
37+
public void onUserFunctionEnd(UserFunctionEndInfo info) {
38+
System.out.println("user function end " + info.name() + " " + info.succeeded());
39+
}
40+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@Override
2+
public void onInvocationStart(InvocationInfo info) {
3+
System.out.println("invocation start " + info.durableExecutionArn()
4+
+ " " + info.isFirstInvocation());
5+
}
6+
7+
@Override
8+
public void onInvocationEnd(InvocationEndInfo info) {
9+
System.out.println("invocation end " + info.invocationStatus());
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@Override
2+
public void onUserFunctionStart(UserFunctionStartInfo info) {
3+
MDC.put("operationName", info.name());
4+
}
5+
6+
@Override
7+
public void onUserFunctionEnd(UserFunctionEndInfo info) {
8+
MDC.remove("operationName");
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@Override
2+
public void onOperationStart(OperationInfo info) {
3+
System.out.println("operation start " + info.type() + " " + info.name());
4+
}
5+
6+
@Override
7+
public void onOperationEnd(OperationEndInfo info) {
8+
System.out.println("operation end " + info.name());
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import software.amazon.lambda.durable.DurableConfig;
2+
import software.amazon.lambda.durable.DurableContext;
3+
import software.amazon.lambda.durable.DurableHandler;
4+
5+
public class ExampleHandler extends DurableHandler<Object, String> {
6+
@Override
7+
protected DurableConfig createConfiguration() {
8+
return DurableConfig.builder()
9+
.withPlugins(new ExamplePlugin())
10+
.build();
11+
}
12+
13+
@Override
14+
protected String handleRequest(Object event, DurableContext context) {
15+
return context.step("process", String.class, stepCtx -> "done");
16+
}
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@Override
2+
public void onUserFunctionStart(UserFunctionStartInfo info) {
3+
System.out.println("user function start " + info.name() + " " + info.attempt());
4+
}
5+
6+
@Override
7+
public void onUserFunctionEnd(UserFunctionEndInfo info) {
8+
System.out.println("user function end " + info.name() + " " + info.succeeded());
9+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from aws_durable_execution_sdk_python.plugin import (
2+
DurableInstrumentationPlugin,
3+
InvocationStartInfo,
4+
InvocationEndInfo,
5+
OperationStartInfo,
6+
OperationEndInfo,
7+
UserFunctionStartInfo,
8+
UserFunctionEndInfo,
9+
)
10+
11+
12+
class ExamplePlugin(DurableInstrumentationPlugin):
13+
def on_invocation_start(self, info: InvocationStartInfo) -> None:
14+
print("invocation start", info.execution_arn, info.is_first_invocation)
15+
16+
def on_invocation_end(self, info: InvocationEndInfo) -> None:
17+
print("invocation end", info.status)
18+
19+
def on_operation_start(self, info: OperationStartInfo) -> None:
20+
print("operation start", info.operation_type, info.name)
21+
22+
def on_operation_end(self, info: OperationEndInfo) -> None:
23+
print("operation end", info.name, info.status, info.error)
24+
25+
def on_user_function_start(self, info: UserFunctionStartInfo) -> None:
26+
print("user function start", info.name, info.attempt)
27+
28+
def on_user_function_end(self, info: UserFunctionEndInfo) -> None:
29+
print("user function end", info.name, info.attempt, info.outcome)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def on_invocation_start(self, info: InvocationStartInfo) -> None:
2+
print("invocation start", info.execution_arn, info.is_first_invocation)
3+
4+
def on_invocation_end(self, info: InvocationEndInfo) -> None:
5+
print("invocation end", info.status)

0 commit comments

Comments
 (0)