Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Record serialized errors without reconstructing application exceptions.

Use a plain exception as the event carrier and explicitly supply the original
checkpointed type, message, and stack trace. Resolving the original class is
unnecessary and could run application imports or constructors during replay.
"""

from __future__ import annotations

from typing import Any

from aws_durable_execution_sdk_python.lambda_service import ErrorObject


_DEFAULT_MESSAGE = "Unknown error"


def exception_from_error_object(error: ErrorObject) -> Exception:
"""Create a plain event carrier; attributes preserve the original details."""
message: str = error.message if error.message is not None else ""
if not error.type:
message = message or _DEFAULT_MESSAGE
Comment thread
zhongkechen marked this conversation as resolved.
return Exception(message)


def error_object_exception_attributes(error: ErrorObject) -> dict[str, Any]:
"""Return explicit event attributes for a checkpointed error.

OTel derives exception attributes from the supplied exception. The plain
carrier has neither the original runtime type nor its traceback, so the
checkpointed values are supplied explicitly.
"""
attributes: dict[str, Any] = {}
if error.type is not None:
attributes["exception.type"] = error.type
if error.message is not None:
attributes["exception.message"] = error.message
if error.stack_trace:
attributes["exception.stacktrace"] = "\n".join(error.stack_trace)
return attributes


def record_error_object_exception(span: Any, error: ErrorObject | None) -> None:
"""Record a span exception that preserves the SDK's original error type.

``error`` may be ``None`` for a failed outcome that carried no metadata; a
generic exception is recorded in that case.
"""
if error is None:
span.record_exception(Exception(_DEFAULT_MESSAGE))
return
span.record_exception(
exception_from_error_object(error),
attributes=error_object_exception_attributes(error),
)
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
ensure_end_after_start,
)
from aws_durable_execution_sdk_python_otel.execution_root import ExecutionRoot
from aws_durable_execution_sdk_python_otel.exception_events import (
record_error_object_exception,
)
from aws_durable_execution_sdk_python_otel.execution_trace_context import (
ExecutionTraceContext,
canonical_trace_id,
Expand Down Expand Up @@ -891,9 +894,7 @@ def on_operation_end(self, info: OperationEndInfo) -> None:

if info.error:
span.set_status(StatusCode.ERROR, info.error.message or "")
span.record_exception(
Exception(info.error.message or info.error.type or "Unknown error")
)
record_error_object_exception(span, info.error)
elif info.status is OperationStatus.SUCCEEDED:
span.set_status(StatusCode.OK)

Expand Down Expand Up @@ -1027,13 +1028,7 @@ def on_user_function_end(self, info: UserFunctionEndInfo) -> None:
span.set_status(
StatusCode.ERROR, info.error.message if info.error else ""
)
span.record_exception(
Exception(
(info.error.message or info.error.type)
if info.error
else "Unknown error"
)
)
record_error_object_exception(span, info.error)
else:
span.set_status(StatusCode.OK)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
from aws_durable_execution_sdk_python_otel.durable_parent_span import (
DurableParentSpan,
)
from aws_durable_execution_sdk_python_otel.exception_events import (
record_error_object_exception,
)
from aws_durable_execution_sdk_python_otel.execution_root import ExecutionRoot
from aws_durable_execution_sdk_python_otel.execution_trace_context import (
ExecutionTraceContext,
Expand Down Expand Up @@ -962,9 +965,7 @@ def on_operation_end(self, info: OperationEndInfo) -> None:

if info.error:
span.set_status(StatusCode.ERROR, info.error.message or "")
span.record_exception(
Exception(info.error.message or info.error.type or "Unknown error")
)
record_error_object_exception(span, info.error)
elif info.status is OperationStatus.SUCCEEDED:
span.set_status(StatusCode.OK)

Expand Down Expand Up @@ -1087,13 +1088,7 @@ def on_user_function_end(self, info: UserFunctionEndInfo) -> None:
span.set_status(
StatusCode.ERROR, info.error.message if info.error else ""
)
span.record_exception(
Exception(
(info.error.message or info.error.type)
if info.error
else "Unknown error"
)
)
record_error_object_exception(span, info.error)
else:
span.set_status(StatusCode.OK)

Expand Down
Loading
Loading