Skip to content

Commit 9b40dec

Browse files
authored
Add a way to disable sandboxing on workflow logger (#1264)
* Add a way to disable sandboxing on workflow logger * Increment stack level to skip override in stack * Extra stacklevel is needed on 3.10 * Lint suppression * Only extra increment if 1
1 parent 23244e0 commit 9b40dec

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

temporalio/workflow.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import contextvars
77
import inspect
88
import logging
9+
import sys
910
import threading
1011
import typing
1112
import uuid
@@ -1581,6 +1582,7 @@ def __init__(self, logger: logging.Logger, extra: Mapping[str, Any] | None) -> N
15811582
self.workflow_info_on_extra = True
15821583
self.full_workflow_info_on_extra = False
15831584
self.log_during_replay = False
1585+
self.disable_sandbox = False
15841586

15851587
def process(
15861588
self, msg: Any, kwargs: MutableMapping[str, Any]
@@ -1614,7 +1616,28 @@ def process(
16141616
kwargs["extra"] = {**extra, **(kwargs.get("extra") or {})}
16151617
if msg_extra:
16161618
msg = f"{msg} ({msg_extra})"
1617-
return (msg, kwargs)
1619+
return msg, kwargs
1620+
1621+
def log(
1622+
self,
1623+
level: int,
1624+
msg: object,
1625+
*args: Any,
1626+
stacklevel: int = 1,
1627+
**kwargs: Any,
1628+
):
1629+
"""Override to potentially disable the sandbox."""
1630+
if sys.version_info < (3, 11) and stacklevel == 1:
1631+
# An additional stacklevel is needed on 3.10 because it doesn't skip internal frames until after stacklevel
1632+
# is decremented, so it needs an additional stacklevel to skip the internal frame.
1633+
stacklevel += 1 # type: ignore[reportUnreachable]
1634+
stacklevel += 1
1635+
if self.disable_sandbox:
1636+
with unsafe.sandbox_unrestricted():
1637+
with unsafe.imports_passed_through():
1638+
super().log(level, msg, *args, stacklevel=stacklevel, **kwargs)
1639+
else:
1640+
super().log(level, msg, *args, stacklevel=stacklevel, **kwargs)
16181641

16191642
def isEnabledFor(self, level: int) -> bool:
16201643
"""Override to ignore replay logs."""
@@ -1629,6 +1652,12 @@ def base_logger(self) -> logging.Logger:
16291652
"""
16301653
return self.logger
16311654

1655+
def unsafe_disable_sandbox(self, value: bool = True):
1656+
"""Disable the sandbox during log processing.
1657+
Can be turned back on with unsafe_disable_sandbox(False).
1658+
"""
1659+
self.disable_sandbox = value
1660+
16321661

16331662
logger = LoggerAdapter(logging.getLogger(__name__), None)
16341663
"""Logger that will have contextual workflow details embedded.

tests/worker/test_workflow.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8434,3 +8434,52 @@ async def test_activity_failure_with_encoded_payload_is_decoded_in_workflow(
84348434
run_timeout=timedelta(seconds=5),
84358435
)
84368436
assert result == "Handled encrypted failure successfully"
8437+
8438+
8439+
@workflow.defn
8440+
class DisableLoggerSandbox:
8441+
@workflow.run
8442+
async def run(self):
8443+
workflow.logger.info("Running workflow")
8444+
8445+
8446+
class CustomLogHandler(logging.Handler):
8447+
def emit(self, record: logging.LogRecord) -> None:
8448+
import httpx # type: ignore[reportUnusedImport]
8449+
8450+
8451+
async def test_disable_logger_sandbox(
8452+
client: Client,
8453+
):
8454+
logger = workflow.logger.logger
8455+
logger.addHandler(CustomLogHandler())
8456+
async with new_worker(
8457+
client,
8458+
DisableLoggerSandbox,
8459+
activities=[],
8460+
) as worker:
8461+
with pytest.raises(WorkflowFailureError):
8462+
await client.execute_workflow(
8463+
DisableLoggerSandbox.run,
8464+
id=f"workflow-{uuid.uuid4()}",
8465+
task_queue=worker.task_queue,
8466+
run_timeout=timedelta(seconds=1),
8467+
retry_policy=RetryPolicy(maximum_attempts=1),
8468+
)
8469+
workflow.logger.unsafe_disable_sandbox()
8470+
await client.execute_workflow(
8471+
DisableLoggerSandbox.run,
8472+
id=f"workflow-{uuid.uuid4()}",
8473+
task_queue=worker.task_queue,
8474+
run_timeout=timedelta(seconds=1),
8475+
retry_policy=RetryPolicy(maximum_attempts=1),
8476+
)
8477+
workflow.logger.unsafe_disable_sandbox(False)
8478+
with pytest.raises(WorkflowFailureError):
8479+
await client.execute_workflow(
8480+
DisableLoggerSandbox.run,
8481+
id=f"workflow-{uuid.uuid4()}",
8482+
task_queue=worker.task_queue,
8483+
run_timeout=timedelta(seconds=1),
8484+
retry_policy=RetryPolicy(maximum_attempts=1),
8485+
)

0 commit comments

Comments
 (0)