Skip to content

feat(insight): add ten Workflow Insight exporters - #704

Merged
wangyb-A merged 1 commit into
mainfrom
feat/insight-exporters-parity
Sep 15, 2026
Merged

wangyb-A merged 1 commit into
mainfrom
feat/insight-exporters-parity

Conversation

@wangyb-A

@wangyb-A wangyb-A commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Issue Link, if available

N/A

Description

Adds ten Workflow Insight exporters to insight-plugin: DynamoDBExporter, AuroraExporter, OTelExporter, FirehoseExporter, EventBridgeExporter, RedshiftExporter, OpenSearchExporter, SQSExporter, HttpExporter, and FileExporter, each behind a builder with defaults matching the other SDKs' insight plugins. A shared OperationsFormat (ARRAY, BY_NAME, BOTH) selects the operations rendering for flexible destinations. AWS SDK clients are created on first use through LazyClient, so a missing optional artifact fails at export with a message naming it and the plugin keeps the other exporters running; S3Exporter and CloudWatchLogsExporter now use the same path. New AWS SDK artifacts in the pom are all <optional>true</optional>; no new required dependency.

Demo/Screenshots

N/A

Checklist

  • I have filled out every section of the PR template
  • I have thoroughly tested this change

Testing

Unit Tests

One test class per exporter under insight-plugin/src/test, asserting exact request fields and bodies through mocked clients (ArgumentCaptor), a loopback HttpServer for OTel/HTTP/OpenSearch (method, headers, body, timeout, SigV4 Authorization presence, basic-auth encoding), and @TempDir for FileExporter (both modes, append semantics, concurrent appends). OperationsFormatTest and LazyClientTest cover the shared helpers. mvn -pl insight-plugin clean install: 141 tests pass. OptionalArtifactTest builds each SDK exporter through a class loader with its service jar removed and asserts build succeeds and export fails naming the artifact. A full mvn clean install passed on the pre-rebase commit; after rebasing on #703 the reactor stopped on an unrelated otel-plugin test (InvocationOtelPluginTest.invocationEnd_closesNestedSpansChildFirst) that passes when run alone.

Integration Tests

Not applicable: exporters call external services and are covered by unit tests with mocked clients.

Examples

Not applicable. README documents each exporter's setup, artifact, IAM actions, and a minimal builder call.

Known follow-ups

  • Add public accessors to WorkflowInsightRecord for emittedAt, endTime, durationMs, region, accountId, functionQualifier, and schemaVersion; exporters currently read these from the rendered wire map.
  • OTelExporter supports http/json only; http/protobuf is rejected at build time.
  • OTelExporter treats any 2xx as delivered and does not parse partialSuccess.rejectedLogRecords.
  • OTelExporter and OpenSearchExporter have no request timeout option; adding one is a cross-SDK config decision.
  • RedshiftExporter submits the statement without waiting for completion (best-effort, by design); later SQL failures are not surfaced.

@wangyb-A

Copy link
Copy Markdown
Contributor Author

/ai review

@github-actions

This comment has been minimized.

@wangyb-A
wangyb-A force-pushed the feat/insight-exporters-parity branch from 360348e to 31041e6 Compare September 11, 2026 23:24
@wangyb-A
wangyb-A marked this pull request as ready for review September 11, 2026 23:48
@wangyb-A
wangyb-A requested a review from a team September 11, 2026 23:48
@wangyb-A
wangyb-A deployed to ai-pr-review-runtime September 11, 2026 23:48 — with GitHub Actions Active
@wangyb-A
wangyb-A deployed to ai-pr-review-runtime September 11, 2026 23:49 — with GitHub Actions Active
@github-actions

This comment has been minimized.

- DynamoDBExporter: PutItem keyed by execution ARN, optional emittedAt sort key, by-name rendering
- AuroraExporter: RDS Data API upsert per execution, PostgreSQL or MySQL dialect
- OTelExporter: one OTLP/HTTP JSON log record per insight record
- FirehoseExporter: one newline-terminated JSON record per PutRecord
- EventBridgeExporter: one PutEvents entry, status as DetailType
- RedshiftExporter: Redshift Data API MERGE per execution
- OpenSearchExporter: index API PUT with SigV4 or basic auth
- SQSExporter: SendMessage with FIFO group and deduplication ids
- HttpExporter: POST or PUT JSON with timeout
- FileExporter: NDJSON append or one pretty JSON file per execution
- OperationsFormat (ARRAY, BY_NAME, BOTH) for flexible destinations
- Optional SDK clients are created on first use; a missing artifact fails with a message naming it
@wangyb-A
wangyb-A force-pushed the feat/insight-exporters-parity branch from 31041e6 to c66fb37 Compare September 14, 2026 20:18
@wangyb-A
wangyb-A deployed to ai-pr-review-runtime September 14, 2026 20:23 — with GitHub Actions Active
Comment on lines +96 to +98
HttpRequest.Builder rb = HttpRequest.newBuilder(endpoint)
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.setHeader("Content-Type", "application/json");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_m6wtujrjs3jdqrxjixqe66bfyh

[P1] Bound these HTTP requests and the equivalent OpenSearch request. Neither client has a connection or request timeout, and onInvocationEnd() drains exporter workers, so an unresponsive peer can hold the Lambda invocation until its own timeout. Add validated timeout options, configure the clients, apply HttpRequest.timeout(), and test slow endpoints.

Comment on lines +106 to +114
redshift.executeStatement(ExecuteStatementRequest.builder()
.workgroupName(workgroupName)
.clusterIdentifier(clusterIdentifier)
.database(database)
.dbUser(dbUser)
.secretArn(secretArn)
.sql(buildMerge(execNameSel, startTimeSel, endTimeSel, durationSel))
.parameters(parameters)
.build());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_zjxpfje7ptkmqdr7dzlabjykge

[P1] Wait for the submitted statement to finish. Redshift Data API's executeStatement returns after queueing asynchronous work, so later SQL or permission failures are lost; an older RUNNING merge can also finish after the terminal merge and overwrite it. Poll DescribeStatement to a terminal status with a bounded timeout, surface failures, document the additional IAM action, and add ordering/failure tests.

Comment on lines +95 to +100
synchronized (appendLock) {
Files.write(
directory.resolve(date + ".ndjson"),
line,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_amm73zbcbjs32xy6zfgxauxtdh

[P1] This in-memory lock does not protect the advertised EFS use case. Concurrent Lambda execution environments have separate FileExporter instances and locks, so their multi-chunk appends to the shared date file can interleave and corrupt NDJSON. Hold a cross-process FileChannel lock for the complete write or use per-execution files, and test independent writers.

headers.forEach(rb::setHeader);
HttpResponse<Void> response;
try {
response = httpClient.send(rb.build(), HttpResponse.BodyHandlers.discarding());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_qiq3jx77pc5jjxmdbtj5gzu5ho

[P2] Do not discard a successful OTLP response body. A 2xx response may contain partialSuccess.rejectedLogRecords, including rejection of this sole record, but this reports delivery as successful. Read and parse the response, fail when any record was rejected, and add a partial-success test.

Comment on lines +103 to +105
String name = record.executionName() != null ? record.executionName() : record.executionArn();
Files.write(
directory.resolve(sanitize(name) + ".json"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_nbydgvplnwukj4ma2x45s3gadq

[P2] Derive JSON filenames from the globally unique execution ARN. Execution names can repeat across functions, and sanitization also maps distinct names to the same filename, causing one execution's record to overwrite another. Include an ARN-derived hash or encoded ARN and test both collision cases.

Comment on lines +59 to +61
item.put(partitionKey, record.executionArn());
if (sortKey != null) {
item.put(sortKey, item.get("emittedAt"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_qyyvkur5ncyvig2l4sf3mbd66h

[P2] Reject identical partition and sort key names. With both configured to the same attribute, the sort-key assignment overwrites the execution ARN with emittedAt, silently changing the item's identity. Validate that enabled key names are distinct at build time and add a rejection test.

Comment on lines +81 to +82
public Object render(WorkflowInsightRecord record) {
return operationsFormat.apply(record);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_n2zzkjbealsdehyxh3k6ponc4o

[P2] Make render() measure the representation JSON mode actually writes. Truncation currently measures compact JSON, while export emits larger pretty-printed JSON, so a configured maxRecordSizeBytes can still be exceeded. Measure the pretty serialization or write the compact representation when enforcing a limit, and add a near-limit JSON-mode test.

@github-actions

Copy link
Copy Markdown
Contributor

Codex AI review

Found seven actionable delivery and correctness issues across the new exporters.

Reviewed commit c66fb370169b3201ca753b03a7ecf3c4f45d8f8f. Workflow run

@ParidelPooya

Copy link
Copy Markdown

OpenSearchExporter.build() throws NoClassDefFoundError when the auth artifact is absent, in both auth modes.

  1. Line 96 reads b.credentialsProvider into a local typed AwsCredentialsProvider.
  2. Line 97 creates a lambda capturing that local, so the invokedynamic call site's type mentions AwsCredentialsProvider. The JVM resolves that class while the constructor runs.
  3. software.amazon.awssdk:auth is declared true, so a consumer classpath can lack it.
  4. Measured with a child-first loader (review-probes/LinkageProbe.java):

=== auth removed
opensearch-sigv4 BUILD FAILED | NoClassDefFoundError: .../auth/credentials/AwsCredentialsProvider
at OpenSearchExporter.(OpenSearchExporter.java:97)
opensearch-basic BUILD FAILED | NoClassDefFoundError: .../auth/credentials/AwsCredentialsProvider

  1. build() runs in the customer's configuration code, not inside the plugin's per-exporter try. So this failure is not contained and function initialization fails.
  2. That contradicts the PR description ("a missing optional artifact fails at export with a message naming it") and the README ("fails at first export with a message naming the artifact").
  3. This is the same mechanism Codex raised as P1 on LazyClient and you fixed for the service clients. OptionalArtifactTest covers the eight service exporters, and OpenSearchExporter is not one of them, so the fix was never checked here.

Fix: hold the provider in an Object field and cast inside Signer, so only Signer's constructor resolves the type. Then add OpenSearchExporter to OptionalArtifactTest with auth and http-auth-aws each removed.

With auth present and only http-auth-aws removed, the mechanism does work: SIGV4 failed at export with Missing dependency software.amazon.awssdk:http-auth-aws, and BASIC exported normally.

@wangyb-A

Copy link
Copy Markdown
Contributor Author

Confirmed and reproduced (same probe shape: child-first loader with auth removed, build() failed in both modes at the constructor's lambda). Fixed in stacked PR #717: the provider is held as Object and cast inside Signer, so only the first SigV4 export links the auth types, and the missing-artifact message names both http-auth-aws and auth. OptionalArtifactTest now covers OpenSearchExporter with http-auth-aws removed, with auth removed (SigV4 fails at export naming the artifacts), and basic auth with auth removed (exports normally).

@wangyb-A
wangyb-A merged commit 0c39b78 into main Sep 15, 2026
35 checks passed
@wangyb-A
wangyb-A deleted the feat/insight-exporters-parity branch September 15, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants