Instrumentation plugin for the AWS Lambda Durable Execution Java SDK that emits a curated,
per-execution Workflow Insight record to one or more pluggable exporters. It ports the
JavaScript workflowInsight() contract (canonical record schema 1.0) to the Java plugin hook
surface.
Preview API. Every public type is annotated
@software.amazon.lambda.durable.annotations.Experimentalto signal it is experimental and may change or be removed in a future release without a major-version bump.
DurableConfig config = DurableConfig.builder()
.withPlugins(WorkflowInsight.workflowInsight(WorkflowInsightConfig.builder()
.samplingRate(1.0)
.emitMode(WorkflowInsightConfig.EmitMode.ON_COMPLETE) // ON_COMPLETE | ON_CHANGE | ON_FAILURE
.operationDetail(WorkflowInsightConfig.OperationDetail.TOP_LEVEL) // TOP_LEVEL | FULL_TREE
.content(ContentConfig.builder()
.input(true).output(true).includeErrors(true)
.addOverride(OperationOverride.withResult("compute", r -> r))
.build())
.addExporter(S3Exporter.builder().bucket("my-bucket").build())
.build()))
.build();Exporters: LambdaLogExporter (default; writes the operationsByName map to stdout →
CloudWatch), S3Exporter (canonical operations array, one object per execution),
CloudWatchLogsExporter (PutLogEvents to a specific log group, operationsByName map). Implement
InsightExporter for custom sinks.
LambdaLogExporter needs no extra dependency. The AWS SDK service modules used by the remote
exporters are optional so applications that use only Lambda logs do not package them. Add the module
for each remote exporter you configure, using the AWS SDK for Java 2.x version managed by your
application:
<!-- Required only for S3Exporter -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>AWS_SDK_VERSION</version>
</dependency>
<!-- Required only for CloudWatchLogsExporter -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cloudwatchlogs</artifactId>
<version>AWS_SDK_VERSION</version>
</dependency>- Snapshot-based, not accumulated. Each record is built directly from the current-invocation
operation snapshot the SDK provides —
InvocationInfo.operations()at start / operation change andInvocationEndInfo.operations()at end. Execution input/output come fromInvocationInfo.executionInput()/InvocationEndInfo.executionResult(), and per-operation results fromOperationChangeItemInfo.result()(all surfaced by SDK PR #618). There is no global "current ARN" or cross-hook operation accumulation. - Per-execution state keyed by execution ARN holds the stable start time, parsed ARN, cached input, and deterministic sampling decision for the current invocation. State is removed after every invocation end, including PENDING/RETRYING, and recreated from stable hook data when the execution resumes.
- Deterministic sampling. FNV-1a-32 over the execution ARN mapped into
[0,1), identical to the JS implementation, so a resumed execution always reaches the same in/out decision. - Emission modes.
ON_COMPLETEemits one terminal record;ON_FAILUREemits only on terminal failure;ON_CHANGEemits at invocation start, on every operation change, and at invocation end (matching JS). Non-terminal statuses map toRUNNING. - Operation filtering mirrors JS: the
EXECUTIONpseudo-operation and unnamed operations are dropped;TOP_LEVELdetail drops any operation with aparentId; anOperationOverride.excludedrops by name. Operationresultis included only when anOperationOverride.withResulttransform opts in — the checkpointed JSON is parsed before the transform, falling back to the raw string, and a throwing transform omits the field. - Content transforms receive detached, JSON-compatible values. The
input/outputtransforms and anOperationOverride.withResulttransform never receive the SDK's original Java object: a POJO is presented as aMap, a list as aList, and a Java-time type as its JSON representation (e.g. anInstantarrives as an ISO-8601String). This is the maintainer's minimum unblock — it is deliberately not a type-preserving clone. Mutating the argument is safe (it cannot corrupt the cached input snapshot or any later emission), and a transform that throws omits the field and logs the failure rather than silently dropping it or failing the execution. includeErrorsgates both the execution-level error and each operation-level error; withincludeErrors(false)neither is emitted, so a sensitive failure message never reaches a record.- Plugin failures never disrupt execution. Every plugin-owned boundary — record construction,
input snapshotting, transforms, truncation, and each exporter's render/export/flush (including a
NoClassDefFoundErrorfrom an optional exporter's absent SDK) — is guarded against anyThrowableand logged, so one failing exporter cannot block the others and no plugin fault propagates into the durable execution. - Per-exporter size truncation (
Truncation) drops, in order: operation results oldest-first, then whole operations oldest-first, then execution input, then output — settingtruncated,droppedOperations,droppedInput,droppedOutputas applicable. The size is measured against the exact shape each exporter emits (itsrender). - Exporter isolation. Every exporter is truncated, exported, and flushed independently; a failing exporter is logged and never blocks the others or the execution.
Validated against the Workflow Insight conformance suite behaviors insight-1 … insight-18
(PR #73 Java examples). See the module tests for the behavior mapping.