fix(job): preserve nested exception details (#544)

This commit is contained in:
jinliyl 2026-09-14 16:54:54 +08:00 committed by GitHub
parent 4f7c8786e3
commit 46eca95bb9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 1 deletions

View file

@ -13,6 +13,25 @@ if TYPE_CHECKING:
from ...steps import BaseStep
def _describe_exception(exc: BaseException) -> str:
"""Render an exception and its causes without dropping empty messages."""
parts: list[str] = []
current: BaseException | None = exc
seen: set[int] = set()
while current is not None and id(current) not in seen:
seen.add(id(current))
message = str(current).strip()
name = type(current).__name__
parts.append(f"{name}: {message}" if message else name)
if current.__cause__ is not None:
current = current.__cause__
elif not current.__suppress_context__:
current = current.__context__
else:
current = None
return " <- ".join(parts)
@R.register("base")
class BaseJob(BaseComponent):
"""Job that executes steps sequentially and returns a Response."""
@ -75,5 +94,5 @@ class BaseJob(BaseComponent):
except Exception as e:
self.logger.exception(f"Failed to execute job: {e}")
context.response.success = False
context.response.answer = str(e)
context.response.answer = _describe_exception(e)
return context.response

View file

@ -71,6 +71,26 @@ def test_call_captures_exception():
asyncio.run(run())
def test_call_preserves_cause_when_outer_exception_message_is_empty():
async def run():
async def failing_step(_context):
try:
raise RuntimeError("connection reset by peer")
except RuntimeError as exc:
raise ConnectionError() from exc
job = BaseJob(name="j")
job.app_context = MagicMock()
job.step_specs = []
job._build_steps = lambda: [failing_step]
response = await job()
assert response.success is False
assert response.answer == ("ConnectionError <- RuntimeError: connection reset by peer")
asyncio.run(run())
def test_call_runs_steps_in_order():
async def run():
call_order = []