Truncate error_summary if exc_info not explicitly passed

Currently, when logging at level >= WARNING, exception information is
added to the log message as an "error summary" for operators. The error
summary includes the exception type and the message. However, when the
exception is a _Remote exception generated from oslo.messaging and sent
over RPC, the full traceback from the exception is appended to the
exception message [1] to preserve it. So, the error summary ends up
outputting the full traceback, unintentionally.

This truncates the error_summary to keep only the first line unless
exc_info was explicitly passed, via LOG.exception(), for example.

Closes-Bug: #1729475

[1] https://github.com/openstack/oslo.messaging/blob/8d42484/oslo_messaging/_drivers/common.py#L208

Change-Id: I2e4e356d23dff2e6ac1946fe9e994c4d962ab55a
This commit is contained in:
melanie witt 2017-12-14 18:37:12 +00:00
parent 97434d48b2
commit a63438cb5b
2 changed files with 31 additions and 0 deletions

View File

@ -156,6 +156,13 @@ def _get_error_summary(record):
exc_info[0], exc_info[0],
exc_info[1], exc_info[1],
)[0].rstrip() )[0].rstrip()
# If the exc_info wasn't explicitly passed to us, take only the
# first line of it. _Remote exceptions from oslo.messaging append
# the full traceback to the exception message, so we want to avoid
# outputting the traceback unless we've been passed exc_info
# directly (via LOG.exception(), for example).
if not record.exc_info:
error_summary = error_summary.split('\n', 1)[0]
except TypeError as type_err: except TypeError as type_err:
# Work around https://bugs.python.org/issue28603 # Work around https://bugs.python.org/issue28603
error_summary = "<exception with %s>" % six.text_type(type_err) error_summary = "<exception with %s>" % six.text_type(type_err)

View File

@ -541,6 +541,30 @@ class JSONFormatterTestCase(LogTestBase):
self.assertIn('error_summary', data) self.assertIn('error_summary', data)
self.assertEqual('', data['error_summary']) self.assertEqual('', data['error_summary'])
def test_exception_without_exc_info_passed(self):
ctxt = _fake_context()
ctxt.request_id = six.text_type('99')
try:
raise RuntimeError('test_exception\ntraceback\nfrom\nremote error')
except RuntimeError:
self.log.warning('testing', context=ctxt)
data = jsonutils.loads(self.stream.getvalue())
self.assertIn('error_summary', data)
self.assertEqual('RuntimeError: test_exception', data['error_summary'])
def test_exception_with_exc_info_passed(self):
ctxt = _fake_context()
ctxt.request_id = six.text_type('99')
try:
raise RuntimeError('test_exception\ntraceback\nfrom\nremote error')
except RuntimeError:
self.log.exception('testing', context=ctxt)
data = jsonutils.loads(self.stream.getvalue())
self.assertIn('error_summary', data)
self.assertEqual('RuntimeError: test_exception'
'\ntraceback\nfrom\nremote error',
data['error_summary'])
def test_fallback(self): def test_fallback(self):
if not formatters._HAVE_JSONUTILS_FALLBACK: if not formatters._HAVE_JSONUTILS_FALLBACK:
self.skipTest("need the fallback parameter of " self.skipTest("need the fallback parameter of "