Skip to content

Commit 8e64278

Browse files
adding sample for assigning severity category for your application error
1 parent 326204a commit 8e64278

6 files changed

Lines changed: 182 additions & 0 deletions

File tree

benign_application_error/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Benign Application Error
2+
This sample shows how to use ApplicationError(category=BENIGN) in the Python SDK.
3+
It demonstrates how the BENIGN error category affects logging severity and metrics emission for activity failures.
4+
5+
BENIGN ApplicationError
6+
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)
7+
No activity failure metrics are emitted.
8+
9+
Non-BENIGN ApplicationError
10+
Activity failure is logged at WARN/ERROR.
11+
Activity failure metrics are emitted.
12+
13+
This makes BENIGN useful for "expected" failure paths where noisy WARN logs and metrics are not desired.
14+
15+
Dependencies
16+
For this sample, the optional python=json-logger dependency group must be included. To include, run:
17+
`uv sync`
18+
19+
Running the Sample
20+
21+
Start the worker in one terminal:
22+
` uv run benign_application_error/worker.py
23+
`
24+
25+
This will start a worker that registers the workflow and activity.
26+
In another terminal, run the starter to execute the workflows:
27+
28+
` uv run benign_application_error/starter.py
29+
`
30+
Expected Behavior
31+
32+
The first workflow runs with BENIGN=True and will not do any logging.
33+
No failure metrics are emitted.
34+
35+
The second workflow runs with BENIGN=False. The activity fails, and the worker logs a WARN entry.
36+
Failure metrics are emitted.
37+
38+
`running worker....
39+
{"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"}}
40+
`
41+
42+
Both workflows will still raise exceptions back to the starter, which you can see printed in the console.
43+
44+
Inspecting Workflows
45+
46+
Use the Temporal CLI to view workflow results:
47+
48+
`temporal workflow show --workflow-id benign_application_error-wf-1
49+
temporal workflow show --workflow-id benign_application_error-wf-2`
50+
51+
52+
Both workflows will show failure status, but only the Non-BENIGN run produces WARN logs and metrics.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import asyncio
2+
from temporalio import activity
3+
from temporalio.exceptions import ApplicationError, ApplicationErrorCategory
4+
5+
@activity.defn
6+
async def greeting_activities(use_benign: bool) -> None:
7+
8+
#BENIGN category errors emit DEBUG level logs and do not record metrics
9+
if use_benign:
10+
raise ApplicationError(
11+
message="With benign flag : Greeting not sent",
12+
category=ApplicationErrorCategory.BENIGN,
13+
)
14+
else:
15+
raise ApplicationError("Without benign flag : Greeting not sent")
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import asyncio
2+
import logging
3+
from pythonjsonlogger import json
4+
from temporalio.client import Client
5+
from benign_application_error.worker import set_init_runtime
6+
from benign_application_error.workflow import BenignApplicationErrorWorkflow
7+
8+
9+
async def main():
10+
runtime = set_init_runtime()
11+
12+
client = await Client.connect(
13+
"localhost:7233",
14+
runtime=runtime,
15+
)
16+
17+
# BENIGN=True
18+
try:
19+
await client.execute_workflow(
20+
BenignApplicationErrorWorkflow.run,
21+
True,
22+
id="benign_application_error-wf-1",
23+
task_queue="benign_application_error_task_queue",
24+
)
25+
except Exception as e:
26+
logging.debug(f"BENIGN=True run finished with exception: {e}")
27+
28+
# BENIGN=False
29+
try:
30+
await client.execute_workflow(
31+
BenignApplicationErrorWorkflow.run,
32+
False,
33+
id="benign_application_error-wf-2",
34+
task_queue="benign_application_error_task_queue",
35+
)
36+
except Exception as e:
37+
logging.debug(f"BENIGN=False run finished with exception: {e}")
38+
39+
40+
41+
if __name__ == "__main__":
42+
asyncio.run(main())

benign_application_error/worker.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import asyncio
2+
import logging
3+
4+
from pythonjsonlogger import json
5+
from temporalio.runtime import Runtime, TelemetryConfig, LogForwardingConfig, LoggingConfig
6+
from temporalio.worker import Worker
7+
from temporalio.client import Client
8+
9+
from benign_application_error.workflow import BenignApplicationErrorWorkflow
10+
from benign_application_error.activities import greeting_activities
11+
12+
13+
def configure_json_logger() -> logging.Logger:
14+
logger = logging.getLogger()
15+
# logger.setLevel(logging.DEBUG) # set level to DEBUG and observe the difference
16+
handler = logging.StreamHandler()
17+
handler.setFormatter(json.JsonFormatter())
18+
logger.handlers.clear()
19+
logger.addHandler(handler)
20+
return logger
21+
22+
def set_init_runtime() -> Runtime:
23+
app_err_logger = configure_json_logger()
24+
25+
return Runtime(
26+
telemetry=TelemetryConfig(
27+
logging=LoggingConfig(
28+
LoggingConfig.default.filter,
29+
forwarding=LogForwardingConfig(logger = app_err_logger),
30+
)
31+
)
32+
)
33+
34+
35+
async def main():
36+
# Configuring logger
37+
runtime = set_init_runtime()
38+
39+
client = await Client.connect(
40+
"localhost:7233",
41+
runtime=runtime,
42+
)
43+
44+
worker = Worker(
45+
client=client,
46+
task_queue="benign_application_error_task_queue",
47+
workflows=[BenignApplicationErrorWorkflow],
48+
activities=[greeting_activities],
49+
)
50+
print("running worker....")
51+
52+
await worker.run()
53+
54+
if __name__ == "__main__":
55+
asyncio.run(main())
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from datetime import timedelta
2+
from temporalio import workflow
3+
from temporalio.common import RetryPolicy
4+
from benign_application_error.activities import greeting_activities
5+
6+
@workflow.defn
7+
class BenignApplicationErrorWorkflow:
8+
@workflow.run
9+
async def run(self, use_benign: bool) -> None:
10+
await workflow.execute_activity(
11+
greeting_activities,
12+
use_benign,
13+
start_to_close_timeout=timedelta(seconds=5),
14+
schedule_to_close_timeout=timedelta(seconds=5),
15+
retry_policy=RetryPolicy(maximum_attempts=1),
16+
)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ dev = [
2626
"types-pyyaml>=6.0.12.20241230,<7",
2727
"pytest-pretty>=1.3.0",
2828
"poethepoet>=0.36.0",
29+
"python-json-logger>=2.0.7",
30+
2931
]
3032
bedrock = ["boto3>=1.34.92,<2"]
3133
dsl = [

0 commit comments

Comments
 (0)