diff --git a/taskflow/conductors/single_threaded.py b/taskflow/conductors/single_threaded.py index d13d666c..6387938e 100644 --- a/taskflow/conductors/single_threaded.py +++ b/taskflow/conductors/single_threaded.py @@ -96,17 +96,24 @@ class SingleThreadedConductor(base.Conductor): engine.run() except excp.WrappedFailure as e: if all((f.check(*NO_CONSUME_EXCEPTIONS) for f in e)): - LOG.warn("Job execution failed (consumption being" - " skipped): %s", job, exc_info=True) consume = False - else: - LOG.warn("Job execution failed: %s", job, exc_info=True) + if LOG.isEnabledFor(logging.WARNING): + if consume: + LOG.warn("Job execution failed (consumption being" + " skipped): %s [%s failures]", job, len(e)) + else: + LOG.warn("Job execution failed (consumption" + " proceeding): %s [%s failures]", job, len(e)) + # Show the failure/s + traceback (if possible)... + for i, f in enumerate(e): + LOG.warn("%s. %s", i + 1, f.pformat(traceback=True)) except NO_CONSUME_EXCEPTIONS: LOG.warn("Job execution failed (consumption being" " skipped): %s", job, exc_info=True) consume = False except Exception: - LOG.warn("Job execution failed: %s", job, exc_info=True) + LOG.warn("Job execution failed (consumption proceeding): %s", + job, exc_info=True) else: LOG.info("Job completed successfully: %s", job) return consume diff --git a/taskflow/tests/unit/test_utils_failure.py b/taskflow/tests/unit/test_utils_failure.py index 012e241a..4958da62 100644 --- a/taskflow/tests/unit/test_utils_failure.py +++ b/taskflow/tests/unit/test_utils_failure.py @@ -42,6 +42,10 @@ class GeneralFailureObjTestsMixin(object): self.assertEqual(list(self.fail_obj), test_utils.RUNTIME_ERROR_CLASSES[:-2]) + def test_pformat_no_traceback(self): + text = self.fail_obj.pformat() + self.assertNotIn("Traceback", text) + def test_check_str(self): val = 'Exception' self.assertEqual(self.fail_obj.check(val), val) @@ -91,6 +95,10 @@ class ReCreatedFailureTestCase(test.TestCase, GeneralFailureObjTestsMixin): def test_no_exc_info(self): self.assertIs(self.fail_obj.exc_info, None) + def test_pformat_traceback(self): + text = self.fail_obj.pformat(traceback=True) + self.assertIn("Traceback (most recent call last):", text) + def test_reraises(self): exc = self.assertRaises(exceptions.WrappedFailure, self.fail_obj.reraise) @@ -103,6 +111,10 @@ class FromExceptionTestCase(test.TestCase, GeneralFailureObjTestsMixin): super(FromExceptionTestCase, self).setUp() self.fail_obj = misc.Failure.from_exception(RuntimeError('Woot!')) + def test_pformat_no_traceback(self): + text = self.fail_obj.pformat(traceback=True) + self.assertIn("Traceback not available", text) + class FailureObjectTestCase(test.TestCase): @@ -188,6 +200,17 @@ class FailureObjectTestCase(test.TestCase): self.assertNotEqual(captured, None) self.assertFalse(captured.matches(None)) + def test_pformat_traceback(self): + captured = _captured_failure('Woot!') + text = captured.pformat(traceback=True) + self.assertIn("Traceback (most recent call last):", text) + + def test_pformat_traceback_captured_no_exc_info(self): + captured = _captured_failure('Woot!') + captured = misc.Failure.from_dict(captured.to_dict()) + text = captured.pformat(traceback=True) + self.assertIn("Traceback (most recent call last):", text) + class WrappedFailureTestCase(test.TestCase): diff --git a/taskflow/utils/misc.py b/taskflow/utils/misc.py index 65f21e24..b716a469 100644 --- a/taskflow/utils/misc.py +++ b/taskflow/utils/misc.py @@ -707,8 +707,23 @@ class Failure(object): return None def __str__(self): - return 'Failure: %s: %s' % (self._exc_type_names[0], - self._exception_str) + return self.pformat() + + def pformat(self, traceback=False): + buf = six.StringIO() + buf.write( + 'Failure: %s: %s' % (self._exc_type_names[0], self._exception_str)) + if traceback: + if self._traceback_str is not None: + traceback_str = self._traceback_str.rstrip() + else: + traceback_str = None + if traceback_str: + buf.write('\nTraceback (most recent call last):\n') + buf.write(traceback_str) + else: + buf.write('\nTraceback not available.') + return buf.getvalue() def __iter__(self): """Iterate over exception type names."""