From 8e64278f55fa3d643584de20a903c95ae4df0a8d Mon Sep 17 00:00:00 2001 From: Deepika Awasthi Date: Thu, 25 Sep 2025 13:32:54 -0700 Subject: [PATCH] adding sample for assigning severity category for your application error --- benign_application_error/README.md | 52 ++++++++++++++++++++++++ benign_application_error/activities.py | 15 +++++++ benign_application_error/starter.py | 42 ++++++++++++++++++++ benign_application_error/worker.py | 55 ++++++++++++++++++++++++++ benign_application_error/workflow.py | 16 ++++++++ pyproject.toml | 2 + 6 files changed, 182 insertions(+) create mode 100644 benign_application_error/README.md create mode 100644 benign_application_error/activities.py create mode 100644 benign_application_error/starter.py create mode 100644 benign_application_error/worker.py create mode 100644 benign_application_error/workflow.py diff --git a/benign_application_error/README.md b/benign_application_error/README.md new file mode 100644 index 00000000..56063f2b --- /dev/null +++ b/benign_application_error/README.md @@ -0,0 +1,52 @@ +# Benign Application Error +This sample shows how to use ApplicationError(category=BENIGN) in the Python SDK. +It demonstrates how the BENIGN error category affects logging severity and metrics emission for activity failures. + +BENIGN ApplicationError +Activity failure is logged only at DEBUG level, otherwise no logging at the logger streaming (uncomment setLevel at line 15 in worker.py to check the logs) +No activity failure metrics are emitted. + +Non-BENIGN ApplicationError +Activity failure is logged at WARN/ERROR. +Activity failure metrics are emitted. + +This makes BENIGN useful for "expected" failure paths where noisy WARN logs and metrics are not desired. + +Dependencies +For this sample, the optional python=json-logger dependency group must be included. To include, run: + `uv sync` + +Running the Sample + +Start the worker in one terminal: +` uv run benign_application_error/worker.py +` + +This will start a worker that registers the workflow and activity. +In another terminal, run the starter to execute the workflows: + +` uv run benign_application_error/starter.py +` +Expected Behavior + +The first workflow runs with BENIGN=True and will not do any logging. +No failure metrics are emitted. + +The second workflow runs with BENIGN=False. The activity fails, and the worker logs a WARN entry. +Failure metrics are emitted. + +`running worker.... +{"message": "Completing activity as failed ({'activity_id': '1', 'activity_type': 'greeting_activities', 'attempt': 1, 'namespace': 'default', 'task_queue': 'benign_application_error_task_queue', 'workflow_id': 'benign_application_error-wf-2', 'workflow_run_id': '0199828f-af08-7a19-ac0f-eed01a7f1974', 'workflow_type': 'BenignApplicationErrorWorkflow'})", "exc_info": "Traceback (most recent call last):\n File \"/Users/deepikaawasthi/temporal/my-local-python-samples/samples-python/.venv/lib/python3.13/site-packages/temporalio/worker/_activity.py\", line 297, in _handle_start_activity_task\n result = await self._execute_activity(start, running_activity, task_token)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/Users/deepikaawasthi/temporal/my-local-python-samples/samples-python/.venv/lib/python3.13/site-packages/temporalio/worker/_activity.py\", line 610, in _execute_activity\n return await impl.execute_activity(input)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/Users/deepikaawasthi/temporal/my-local-python-samples/samples-python/.venv/lib/python3.13/site-packages/temporalio/worker/_activity.py\", line 805, in execute_activity\n return await input.fn(*input.args)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/Users/deepikaawasthi/temporal/my-local-python-samples/samples-python/benign_application_error/activities.py\", line 15, in greeting_activities\n raise ApplicationError(\"Without benign flag : Greeting not sent\")\ntemporalio.exceptions.ApplicationError: Without benign flag : Greeting not sent", "temporal_activity": {"activity_id": "1", "activity_type": "greeting_activities", "attempt": 1, "namespace": "default", "task_queue": "benign_application_error_task_queue", "workflow_id": "benign_application_error-wf-2", "workflow_run_id": "0199828f-af08-7a19-ac0f-eed01a7f1974", "workflow_type": "BenignApplicationErrorWorkflow"}} +` + +Both workflows will still raise exceptions back to the starter, which you can see printed in the console. + +Inspecting Workflows + +Use the Temporal CLI to view workflow results: + +`temporal workflow show --workflow-id benign_application_error-wf-1 +temporal workflow show --workflow-id benign_application_error-wf-2` + + +Both workflows will show failure status, but only the Non-BENIGN run produces WARN logs and metrics. \ No newline at end of file diff --git a/benign_application_error/activities.py b/benign_application_error/activities.py new file mode 100644 index 00000000..caaf4673 --- /dev/null +++ b/benign_application_error/activities.py @@ -0,0 +1,15 @@ +import asyncio +from temporalio import activity +from temporalio.exceptions import ApplicationError, ApplicationErrorCategory + +@activity.defn +async def greeting_activities(use_benign: bool) -> None: + + #BENIGN category errors emit DEBUG level logs and do not record metrics + if use_benign: + raise ApplicationError( + message="With benign flag : Greeting not sent", + category=ApplicationErrorCategory.BENIGN, + ) + else: + raise ApplicationError("Without benign flag : Greeting not sent") diff --git a/benign_application_error/starter.py b/benign_application_error/starter.py new file mode 100644 index 00000000..61374139 --- /dev/null +++ b/benign_application_error/starter.py @@ -0,0 +1,42 @@ +import asyncio +import logging +from pythonjsonlogger import json +from temporalio.client import Client +from benign_application_error.worker import set_init_runtime +from benign_application_error.workflow import BenignApplicationErrorWorkflow + + +async def main(): + runtime = set_init_runtime() + + client = await Client.connect( + "localhost:7233", + runtime=runtime, + ) + + # BENIGN=True + try: + await client.execute_workflow( + BenignApplicationErrorWorkflow.run, + True, + id="benign_application_error-wf-1", + task_queue="benign_application_error_task_queue", + ) + except Exception as e: + logging.debug(f"BENIGN=True run finished with exception: {e}") + + # BENIGN=False + try: + await client.execute_workflow( + BenignApplicationErrorWorkflow.run, + False, + id="benign_application_error-wf-2", + task_queue="benign_application_error_task_queue", + ) + except Exception as e: + logging.debug(f"BENIGN=False run finished with exception: {e}") + + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/benign_application_error/worker.py b/benign_application_error/worker.py new file mode 100644 index 00000000..dbacef2a --- /dev/null +++ b/benign_application_error/worker.py @@ -0,0 +1,55 @@ +import asyncio +import logging + +from pythonjsonlogger import json +from temporalio.runtime import Runtime, TelemetryConfig, LogForwardingConfig, LoggingConfig +from temporalio.worker import Worker +from temporalio.client import Client + +from benign_application_error.workflow import BenignApplicationErrorWorkflow +from benign_application_error.activities import greeting_activities + + +def configure_json_logger() -> logging.Logger: + logger = logging.getLogger() +# logger.setLevel(logging.DEBUG) # set level to DEBUG and observe the difference + handler = logging.StreamHandler() + handler.setFormatter(json.JsonFormatter()) + logger.handlers.clear() + logger.addHandler(handler) + return logger + +def set_init_runtime() -> Runtime: + app_err_logger = configure_json_logger() + + return Runtime( + telemetry=TelemetryConfig( + logging=LoggingConfig( + LoggingConfig.default.filter, + forwarding=LogForwardingConfig(logger = app_err_logger), + ) + ) + ) + + +async def main(): + # Configuring logger + runtime = set_init_runtime() + + client = await Client.connect( + "localhost:7233", + runtime=runtime, + ) + + worker = Worker( + client=client, + task_queue="benign_application_error_task_queue", + workflows=[BenignApplicationErrorWorkflow], + activities=[greeting_activities], + ) + print("running worker....") + + await worker.run() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/benign_application_error/workflow.py b/benign_application_error/workflow.py new file mode 100644 index 00000000..5e2315eb --- /dev/null +++ b/benign_application_error/workflow.py @@ -0,0 +1,16 @@ +from datetime import timedelta +from temporalio import workflow +from temporalio.common import RetryPolicy +from benign_application_error.activities import greeting_activities + +@workflow.defn +class BenignApplicationErrorWorkflow: + @workflow.run + async def run(self, use_benign: bool) -> None: + await workflow.execute_activity( + greeting_activities, + use_benign, + start_to_close_timeout=timedelta(seconds=5), + schedule_to_close_timeout=timedelta(seconds=5), + retry_policy=RetryPolicy(maximum_attempts=1), + ) diff --git a/pyproject.toml b/pyproject.toml index 3a73a8d1..2ab27609 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,8 @@ dev = [ "types-pyyaml>=6.0.12.20241230,<7", "pytest-pretty>=1.3.0", "poethepoet>=0.36.0", + "python-json-logger>=2.0.7", + ] bedrock = ["boto3>=1.34.92,<2"] dsl = [