Skip to content
Open
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
52 changes: 52 additions & 0 deletions benign_application_error/README.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions benign_application_error/activities.py
Original file line number Diff line number Diff line change
@@ -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")
42 changes: 42 additions & 0 deletions benign_application_error/starter.py
Original file line number Diff line number Diff line change
@@ -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())
55 changes: 55 additions & 0 deletions benign_application_error/worker.py
Original file line number Diff line number Diff line change
@@ -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())
16 changes: 16 additions & 0 deletions benign_application_error/workflow.py
Original file line number Diff line number Diff line change
@@ -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),
)
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
Loading