-
Notifications
You must be signed in to change notification settings - Fork 22
feat: add Workflow Insight instrumentation plugin #632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
wangyb-A
wants to merge
2
commits into
main
Choose a base branch
from
feat/workflow-insight-plugin
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
55 changes: 55 additions & 0 deletions
55
packages/aws-durable-execution-sdk-python-insight/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # AWS Durable Execution SDK for Python — Workflow Insight plugin | ||
|
|
||
| Workflow Insight instrumentation plugin for the AWS Durable Execution SDK for | ||
| Python. A port of the JavaScript SDK's `workflowInsight()` plugin: it listens to | ||
| the SDK's instrumentation hooks and emits one curated `WorkflowInsight` record | ||
| per execution to the configured exporters. The wire record keeps the JS | ||
| camelCase field names so records read identically across SDKs. | ||
|
|
||
| > **Experimental.** Like its JS counterpart, this plugin is experimental and may | ||
| > change or be removed in future releases. | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| pip install aws-durable-execution-sdk-python-insight | ||
| # with the S3 exporter's local-dev dependency: | ||
| pip install "aws-durable-execution-sdk-python-insight[s3]" | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ```python | ||
| from aws_durable_execution_sdk_python import durable_execution | ||
| from aws_durable_execution_sdk_python_insight import workflow_insight | ||
| from aws_durable_execution_sdk_python_insight.exporters import S3Exporter | ||
|
|
||
| @durable_execution( | ||
| plugins=[ | ||
| workflow_insight( | ||
| exporters=[S3Exporter(bucket="my-bucket", prefix="workflow-insight/")], | ||
| ) | ||
| ] | ||
| ) | ||
| def handler(event, context): | ||
| ... | ||
| ``` | ||
|
|
||
| With no exporter configured, records are written to the function's own | ||
| CloudWatch log group as single JSON lines (the `LambdaLogExporter` default), | ||
| carrying the name-keyed `operationsByName` summary. The `S3Exporter` writes the | ||
| lossless per-occurrence `operations` array, one object per execution | ||
| (upsert-by-execution-name, so re-emission overwrites rather than appends). | ||
|
|
||
| Emission behavior, record schema (`recordType: WorkflowInsight`, | ||
| `schemaVersion: "1.0"`), sampling, content configuration (input/output | ||
| omission, `include_errors`, per-operation result opt-in), truncation phases, | ||
| and `top-level` vs `full-tree` operation detail all mirror the JS plugin. | ||
| Behavior is validated cross-SDK by the `insight` conformance suite | ||
| (`aws-durable-execution-conformance-tests-insight`). | ||
|
|
||
| ## Requirements | ||
|
|
||
| - `aws-durable-execution-sdk-python` with the plugin invocation hooks that | ||
| surface `execution_input` / `execution_result` (included since the version | ||
| this package declares as its minimum). |
79 changes: 79 additions & 0 deletions
79
packages/aws-durable-execution-sdk-python-insight/pyproject.toml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [project] | ||
| name = "aws-durable-execution-sdk-python-insight" | ||
| dynamic = ["version"] | ||
| description = 'Workflow Insight instrumentation plugin for the AWS Durable Execution SDK for Python' | ||
| readme = "README.md" | ||
| requires-python = ">=3.11" | ||
| license = "Apache-2.0" | ||
| keywords = ["observability", "workflow-insight", "durable-execution"] | ||
| authors = [{ name = "AWS durable-execution-dev", email = "durable-execution-dev@amazon.com" }] | ||
| classifiers = [ | ||
| "Development Status :: 4 - Beta", | ||
| "Programming Language :: Python", | ||
| "Programming Language :: Python :: 3.11", | ||
| "Programming Language :: Python :: 3.12", | ||
| "Programming Language :: Python :: 3.13", | ||
| "Programming Language :: Python :: 3.14", | ||
| "Programming Language :: Python :: Implementation :: CPython", | ||
| ] | ||
| dependencies = [ | ||
| # >=1.8.0: first release carrying the plugin invocation-hook fields | ||
| # (InvocationInfo.execution_input / InvocationEndInfo.execution_result). | ||
| "aws-durable-execution-sdk-python>=1.8.0", | ||
| ] | ||
|
|
||
| [project.optional-dependencies] | ||
| # boto3 is provided by the Lambda runtime; declared as an extra for local dev | ||
| # (e.g. the S3Exporter) without vendoring it into deployments. | ||
| s3 = ["boto3>=1.26.0"] | ||
|
|
||
| [project.urls] | ||
| Documentation = "https://github.com/aws/aws-durable-execution-sdk-python#readme" | ||
| Issues = "https://github.com/aws/aws-durable-execution-sdk-python/issues" | ||
| Source = "https://github.com/aws/aws-durable-execution-sdk-python" | ||
|
|
||
| [tool.hatch.build.targets.sdist.force-include] | ||
| "../../LICENSE" = "LICENSE" | ||
| "../../NOTICE" = "NOTICE" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["src/aws_durable_execution_sdk_python_insight"] | ||
|
|
||
| [tool.hatch.build.targets.wheel.force-include] | ||
| "../../LICENSE" = "aws_durable_execution_sdk_python_insight/LICENSE" | ||
| "../../NOTICE" = "aws_durable_execution_sdk_python_insight/NOTICE" | ||
|
|
||
| [tool.hatch.version] | ||
| path = "src/aws_durable_execution_sdk_python_insight/__about__.py" | ||
|
|
||
| [tool.hatch.publish.index] | ||
| disable = true | ||
|
|
||
| [tool.coverage.run] | ||
| source_pkgs = ["aws_durable_execution_sdk_python_insight"] | ||
| branch = true | ||
| parallel = true | ||
| omit = ["src/aws_durable_execution_sdk_python_insight/__about__.py"] | ||
|
|
||
| [tool.coverage.report] | ||
| exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"] | ||
|
|
||
| [tool.ruff] | ||
| line-length = 88 | ||
| target-version = "py311" | ||
|
|
||
| [tool.ruff.lint] | ||
| preview = true | ||
| select = ["E4", "E7", "E9", "F", "TID252"] | ||
|
|
||
| [tool.ruff.lint.isort] | ||
| known-first-party = ["aws_durable_execution_sdk_python_insight"] | ||
| force-single-line = false | ||
| lines-after-imports = 2 | ||
|
|
||
| [tool.ruff.lint.per-file-ignores] | ||
| "tests/**" = ["ARG001", "ARG002", "ARG005", "S101", "PLR2004", "PLR6301", "SIM117", "TRY301"] | ||
4 changes: 4 additions & 0 deletions
4
...le-execution-sdk-python-insight/src/aws_durable_execution_sdk_python_insight/__about__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # SPDX-FileCopyrightText: 2026-present Amazon.com, Inc. or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| __version__ = "0.0.1" |
43 changes: 43 additions & 0 deletions
43
...ble-execution-sdk-python-insight/src/aws_durable_execution_sdk_python_insight/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # SPDX-FileCopyrightText: 2026-present Amazon.com, Inc. or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Workflow Insight instrumentation plugin for the AWS Durable Execution Python SDK.""" | ||
|
|
||
| from aws_durable_execution_sdk_python_insight.__about__ import __version__ | ||
| from aws_durable_execution_sdk_python_insight.exporters import ( | ||
| LambdaLogExporter, | ||
| S3Exporter, | ||
| ) | ||
| from aws_durable_execution_sdk_python_insight.operations_index import ( | ||
| build_operations_by_name, | ||
| with_operations_by_name, | ||
| ) | ||
| from aws_durable_execution_sdk_python_insight.plugin import ( | ||
| WorkflowInsightPlugin, | ||
| workflow_insight, | ||
| ) | ||
| from aws_durable_execution_sdk_python_insight.truncation import truncate_record | ||
| from aws_durable_execution_sdk_python_insight.types import ( | ||
| ContentConfig, | ||
| ContentOperations, | ||
| InsightExporter, | ||
| OperationOverride, | ||
| WorkflowInsightConfig, | ||
| ) | ||
|
|
||
|
|
||
| __all__ = [ | ||
| "__version__", | ||
| "ContentConfig", | ||
| "ContentOperations", | ||
| "InsightExporter", | ||
| "LambdaLogExporter", | ||
| "OperationOverride", | ||
| "S3Exporter", | ||
| "WorkflowInsightConfig", | ||
| "WorkflowInsightPlugin", | ||
| "build_operations_by_name", | ||
| "truncate_record", | ||
| "with_operations_by_name", | ||
| "workflow_insight", | ||
| ] |
36 changes: 36 additions & 0 deletions
36
...ion-sdk-python-insight/src/aws_durable_execution_sdk_python_insight/exporters/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # SPDX-FileCopyrightText: 2026-present Amazon.com, Inc. or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """First-party Workflow Insight exporters. | ||
|
|
||
| One module per exporter, mirroring the JS package's ``src/exporters/`` layout | ||
| (``aws-durable-execution-sdk-js-insight``). Each destination lives in its own | ||
| module so the set can grow to the full JS parity surface (S3, CloudWatch Logs, | ||
| DynamoDB, Firehose, EventBridge, SQS, OpenSearch, Redshift, Aurora, HTTP, OTel, | ||
| file, ...) without any single file accreting every backend's imports and | ||
| optional dependencies. | ||
|
|
||
| Concrete exporters are re-exported here so the public import path is stable: | ||
| ``from aws_durable_execution_sdk_python_insight.exporters import S3Exporter`` | ||
| keeps working exactly as before this package was split out of a single module. | ||
| Shared serialization helpers live in the private ``_common`` module. | ||
|
|
||
| Both shipped exporters serialize the curated record with JS-compatible compact | ||
| JSON (no whitespace) so the wire bytes match across SDKs. Records are written | ||
| verbatim -- no synthetic emission. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from aws_durable_execution_sdk_python_insight.exporters.lambda_log_exporter import ( | ||
| LambdaLogExporter, | ||
| ) | ||
| from aws_durable_execution_sdk_python_insight.exporters.s3_exporter import ( | ||
| S3Exporter, | ||
| ) | ||
|
|
||
|
|
||
| __all__ = [ | ||
| "LambdaLogExporter", | ||
| "S3Exporter", | ||
| ] |
29 changes: 29 additions & 0 deletions
29
...tion-sdk-python-insight/src/aws_durable_execution_sdk_python_insight/exporters/_common.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # SPDX-FileCopyrightText: 2026-present Amazon.com, Inc. or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Shared serialization helpers for the Workflow Insight exporters. | ||
|
|
||
| Kept private to the ``exporters`` package: every backend needs the same | ||
| JS-compatible compact JSON encoding and the same key/file-name sanitizer, so | ||
| they live here rather than being duplicated per exporter module. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import re | ||
| from typing import Any | ||
|
|
||
|
|
||
| def compact_dumps(value: Any) -> str: | ||
| """Serialize ``value`` as compact JSON (no whitespace, non-ASCII preserved). | ||
|
|
||
| Matches the JS exporters' ``JSON.stringify`` output so the wire bytes are | ||
| identical across SDKs. | ||
| """ | ||
| return json.dumps(value, separators=(",", ":"), ensure_ascii=False) | ||
|
|
||
|
|
||
| def sanitize(value: str) -> str: | ||
| """Replace characters unsafe for object keys / file names with ``_``.""" | ||
| return re.sub(r"[^a-zA-Z0-9._-]", "_", value) |
39 changes: 39 additions & 0 deletions
39
...hon-insight/src/aws_durable_execution_sdk_python_insight/exporters/lambda_log_exporter.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # SPDX-FileCopyrightText: 2026-present Amazon.com, Inc. or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Lambda log (CloudWatch) Workflow Insight exporter.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| from aws_durable_execution_sdk_python_insight.exporters._common import compact_dumps | ||
| from aws_durable_execution_sdk_python_insight.operations_index import ( | ||
| with_operations_by_name, | ||
| ) | ||
|
|
||
|
|
||
| class LambdaLogExporter: | ||
| """Writes ``operationsByName`` records to the function's own log group via ``print``. | ||
|
|
||
| Port of the JS ``LambdaLogExporter``: ``console.log(JSON.stringify( | ||
| withOperationsByName(record)))``. Requires no extra IAM. Emits the name-keyed | ||
| summary map (``OPERATIONS_BY_NAME``). | ||
| """ | ||
|
|
||
| def __init__(self, max_record_size_bytes: int | None = None) -> None: | ||
| self.max_record_size_bytes = ( | ||
| 256_000 if max_record_size_bytes is None else max_record_size_bytes | ||
| ) | ||
|
|
||
| def render(self, record: dict[str, Any]) -> dict[str, Any]: | ||
| return with_operations_by_name(record) | ||
|
|
||
| def export(self, record: dict[str, Any]) -> None: | ||
| # Raw JSON line to stdout -> the function's CloudWatch log group. The | ||
| # conformance CloudWatch sink json.loads each line (and unwraps the | ||
| # Lambda structured-log envelope when present). | ||
| print(compact_dumps(self.render(record)), flush=True) # noqa: T201 | ||
|
|
||
| def flush(self) -> None: | ||
| return None |
81 changes: 81 additions & 0 deletions
81
...-sdk-python-insight/src/aws_durable_execution_sdk_python_insight/exporters/s3_exporter.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| # SPDX-FileCopyrightText: 2026-present Amazon.com, Inc. or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """S3 Workflow Insight exporter.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| from aws_durable_execution_sdk_python_insight.exporters._common import ( | ||
| compact_dumps, | ||
| sanitize, | ||
| ) | ||
|
|
||
|
|
||
| class S3Exporter: | ||
| """Writes canonical ``operations``-array records to S3. | ||
|
|
||
| Port of the JS ``S3Exporter``. Each record is a JSON object keyed by | ||
| execution name, so updates to the same execution overwrite the same object. | ||
| Emits the lossless ``operations`` array (``OPERATIONS_ARRAY``). | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| bucket: str, | ||
| prefix: str = "workflow-insight/", | ||
| partitioning: str = "date", | ||
| region: str | None = None, | ||
| max_record_size_bytes: int | None = None, | ||
| client: Any = None, | ||
| ) -> None: | ||
| self.bucket = bucket | ||
| self.prefix = prefix | ||
| self.partitioning = partitioning | ||
| self.max_record_size_bytes = ( | ||
| 5_000_000 if max_record_size_bytes is None else max_record_size_bytes | ||
| ) | ||
| if client is not None: | ||
| self._client = client | ||
| else: | ||
| import boto3 # deferred: boto3 is provided by the Lambda runtime | ||
|
|
||
| self._client = ( | ||
| boto3.client("s3", region_name=region) if region else boto3.client("s3") | ||
| ) | ||
|
|
||
| def render(self, record: dict[str, Any]) -> dict[str, Any]: | ||
| return record | ||
|
|
||
| def export(self, record: dict[str, Any]) -> None: | ||
| key = self._build_key(record) | ||
| self._client.put_object( | ||
| Bucket=self.bucket, | ||
| Key=key, | ||
| Body=compact_dumps(record).encode("utf-8"), | ||
| ContentType="application/json", | ||
| ) | ||
|
|
||
| def flush(self) -> None: | ||
| return None | ||
|
|
||
| def _build_key(self, record: dict[str, Any]) -> str: | ||
| file_name = ( | ||
| sanitize( | ||
| record.get("executionName") or record.get("executionArn") or "record" | ||
| ) | ||
| + ".json" | ||
| ) | ||
| return f"{self.prefix}{self._partition(record)}{file_name}" | ||
|
|
||
| def _partition(self, record: dict[str, Any]) -> str: | ||
| if self.partitioning == "function-name": | ||
| return f"function={sanitize(record.get('functionName', ''))}/" | ||
| if self.partitioning == "date": | ||
| start = str(record.get("startTime", "")) | ||
| # YYYY-MM-DD... -> year=YYYY/month=MM/day=DD/ | ||
| if len(start) >= 10 and start[4] == "-" and start[7] == "-": | ||
| return f"year={start[0:4]}/month={start[5:7]}/day={start[8:10]}/" | ||
| return "" | ||
| return "" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codex AI review
High: The repository's PyPI workflow still enumerates only core, OTel, and testing packages. This distribution will therefore never be built or uploaded by a release, making the documented
pip installunavailable. Add it to both release matrices and distribution legal-file verification.