diff options
| author | Aaron Lehmann <aaron@zope.com> | 2008-01-22 16:24:16 +0000 |
|---|---|---|
| committer | Aaron Lehmann <aaron@zope.com> | 2008-01-22 16:24:16 +0000 |
| commit | 63eb682afe4742bebfdb3092a3e57d1e03336035 (patch) | |
| tree | 39b249ce844a73b430690bdc3f0136939e405bc9 /src | |
| parent | 89ecd3a98e1b90761c64decd83886d386e503128 (diff) | |
| download | zope-exceptions-63eb682afe4742bebfdb3092a3e57d1e03336035.tar.gz | |
Add capability for line-by-line formatting of exceptions.
Diffstat (limited to 'src')
| -rw-r--r-- | src/zope/exceptions/exceptionformatter.py | 45 | ||||
| -rw-r--r-- | src/zope/exceptions/log.py | 47 | ||||
| -rw-r--r-- | src/zope/exceptions/tests/test_exceptionformatter.py | 64 |
3 files changed, 130 insertions, 26 deletions
diff --git a/src/zope/exceptions/exceptionformatter.py b/src/zope/exceptions/exceptionformatter.py index 4efa0fa..430dde7 100644 --- a/src/zope/exceptions/exceptionformatter.py +++ b/src/zope/exceptions/exceptionformatter.py @@ -28,15 +28,16 @@ class TextExceptionFormatter(object): line_sep = '\n' show_revisions = 0 - def __init__(self, limit=None, with_filenames=False): + def __init__(self, limit=None, with_filenames=False, format=None): self.limit = limit self.with_filenames = with_filenames + self.format = format def escape(self, s): return s def getPrefix(self): - return 'Traceback (most recent call last):' + return self.formatText('Traceback (most recent call last):') def getLimit(self): limit = self.limit @@ -118,12 +119,12 @@ class TextExceptionFormatter(object): s = s + ', in %s' % name result = [] - result.append(self.escape(s)) + result.append(self.escape(self.formatText(s))) # Append the source line, if available line = linecache.getline(filename, lineno) if line: - result.append(" " + self.escape(line.strip())) + result.append(self.escape(self.formatText(" " + line.strip()))) # Output a traceback supplement, if any. if '__traceback_supplement__' in locals: @@ -145,7 +146,6 @@ class TextExceptionFormatter(object): if DEBUG_EXCEPTION_FORMATTER: traceback.print_exc() # else just swallow the exception. - try: tbi = locals.get('__traceback_info__', None) if tbi is not None: @@ -158,13 +158,25 @@ class TextExceptionFormatter(object): return self.line_sep.join(result) def formatExceptionOnly(self, etype, value): - result = ''.join(traceback.format_exception_only(etype, value)) - return result.replace('\n', self.line_sep) + result = ''.join( + [self.formatText(line) for line in traceback.format_exception_only(etype, value)] + # traceback.format_exception_only(etype, value) + ) + return result def formatLastLine(self, exc_line): return self.escape(exc_line) - def formatException(self, etype, value, tb): + def formatText(self, text): + if self.format: + args = self.format[1].__dict__.copy() + args['message'] = text + line = self.format[0] % args + else: + line = text + return line + + def formatException(self, etype, value, tb, format=None): # The next line provides a way to detect recursion. __exception_formatter__ = 1 result = [self.getPrefix() + '\n'] @@ -189,10 +201,11 @@ class HTMLExceptionFormatter(TextExceptionFormatter): line_sep = '<br />\r\n' def escape(self, s): - return cgi.escape(s) + return cgi.escape(super(HTMLExceptionFormatter, self).escape(s)) def getPrefix(self): - return '<p>Traceback (most recent call last):\r\n<ul>' + return '<p>%s\r\n<ul>' % super( + HTMLExceptionFormatter, self).getPrefix() def formatSupplementLine(self, line): return '<b>%s</b>' % self.escape(str(line)) @@ -207,11 +220,11 @@ class HTMLExceptionFormatter(TextExceptionFormatter): return '<li>%s</li>' % line def formatLastLine(self, exc_line): - return '</ul>%s</p>' % self.escape(exc_line) + return '</ul>%s</p>' % self.escape(exc_line).replace('\n', self.line_sep) def format_exception(t, v, tb, limit=None, as_html=False, - with_filenames=False): + with_filenames=False, format=None): """Format a stack trace and the exception information. Similar to 'traceback.format_exception', but adds supplemental @@ -219,14 +232,14 @@ def format_exception(t, v, tb, limit=None, as_html=False, and 'with_filenames'. """ if as_html: - fmt = HTMLExceptionFormatter(limit, with_filenames) + fmt = HTMLExceptionFormatter(limit, with_filenames, format=format) else: - fmt = TextExceptionFormatter(limit, with_filenames) + fmt = TextExceptionFormatter(limit, with_filenames, format=format) return fmt.formatException(t, v, tb) def print_exception(t, v, tb, limit=None, file=None, as_html=False, - with_filenames=True): + with_filenames=True, format=None): """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. Similar to 'traceback.print_exception', but adds supplemental @@ -235,6 +248,6 @@ def print_exception(t, v, tb, limit=None, file=None, as_html=False, """ if file is None: file = sys.stderr - lines = format_exception(t, v, tb, limit, as_html, with_filenames) + lines = format_exception(t, v, tb, limit, as_html, with_filenames, format=format) for line in lines: file.write(line) diff --git a/src/zope/exceptions/log.py b/src/zope/exceptions/log.py index 0fa282f..e77c1b5 100644 --- a/src/zope/exceptions/log.py +++ b/src/zope/exceptions/log.py @@ -13,7 +13,7 @@ ############################################################################## """Log formatter that enhances tracebacks with extra information. -$Id: $ +$Id$ """ import logging @@ -21,14 +21,55 @@ import cStringIO from zope.exceptions.exceptionformatter import print_exception class Formatter(logging.Formatter): + def __init__(self, fmt=None, datefmt=None, as_html=0,): + logging.Formatter.__init__(self, fmt=fmt, datefmt=datefmt) + self.as_html = as_html - def formatException(self, ei): + def format(self, record): + record.message = record.getMessage() + if self._fmt.find("%(asctime)") >= 0: + record.asctime = self.formatTime(record, self.datefmt) + cdict = record.__dict__.copy() + lines = [] + for line in record.message.splitlines(): + cdict['message'] = line + lines.append(self._fmt % cdict) + s = '\n'.join(lines) + if record.exc_info: + # Cache the traceback text to avoid converting it multiple times + # (it's constant anyway) + if not record.exc_text: + record.exc_text = self.formatException(record.exc_info, record=record) + if record.exc_text: + s = record.exc_text + else: + cdict = record.__dict__.copy() + lines = [] + for line in record.message.splitlines(): + cdict['message'] = line + lines.append(self._fmt % cdict) + s = '\n'.join(lines) + return s + + def formatException(self, ei, record=None): """Format and return the specified exception information as a string. Uses zope.exceptions.exceptionformatter to generate the traceback. """ sio = cStringIO.StringIO() - print_exception(ei[0], ei[1], ei[2], file=sio, with_filenames=True) + if record: + format = (self._fmt, record) + else: + format = None + + print_exception( + ei[0], + ei[1], + ei[2], + file=sio, + as_html=self.as_html, + with_filenames=True, + format=format) s = sio.getvalue() if s.endswith("\n"): s = s[:-1] diff --git a/src/zope/exceptions/tests/test_exceptionformatter.py b/src/zope/exceptions/tests/test_exceptionformatter.py index 98b75b7..4824c60 100644 --- a/src/zope/exceptions/tests/test_exceptionformatter.py +++ b/src/zope/exceptions/tests/test_exceptionformatter.py @@ -16,18 +16,30 @@ $Id$ """ import sys -from unittest import TestCase, main, makeSuite +import unittest +import zope.testing.doctest +import zope.exceptions.log from zope.exceptions.exceptionformatter import format_exception from zope.testing.cleanup import CleanUp # Base class w registry cleanup +import logging +import StringIO + +def tb(as_html=0, format=None): + log = logging.getLogger('') + fake_stdout = StringIO.StringIO() + hndler = logging.StreamHandler(fake_stdout) + hndler.setFormatter(zope.exceptions.log.Formatter(as_html=as_html, fmt=format)) + log.addHandler(hndler) -def tb(as_html=0): t, v, b = sys.exc_info() try: - return ''.join(format_exception(t, v, b, as_html=as_html)) + log.exception('') + s = fake_stdout.getvalue() + return s finally: del b - + log.removeHandler(hndler) class ExceptionForTesting (Exception): pass @@ -46,7 +58,7 @@ class TestingTracebackSupplement(object): -class Test(CleanUp, TestCase): +class Test(CleanUp, unittest.TestCase): def testBasicNamesText(self, as_html=0): try: @@ -160,9 +172,47 @@ class Test(CleanUp, TestCase): ' ^', 'SyntaxError: unexpected EOF while parsing']) + def testFormattedExceptionText(self): + try: + exec 'syntax error' + except SyntaxError, se: + s = tb(format="Hello, World! %(message)s") + # Hello, World! Traceback (most recent call last): + # Hello, World! Module zope.exceptions.tests.test_exceptionformatter, line ??, in testFormattedExceptionText + # Hello, World! exec \'syntax error\' + # Hello, World! File "<string>", line 1 + # Hello, World! syntax error + # Hello, World! ^ + # Hello, World! SyntaxError: unexpected EOF while parsing + self.assertEquals(s.splitlines()[-3:], + ['Hello, World! syntax error', + 'Hello, World! ^', + 'Hello, World! SyntaxError: unexpected EOF while parsing']) + + def testFormattedExceptionHTML(self): + try: + exec 'syntax error' + except SyntaxError, se: + s = tb(as_html=1, format="Hello, World! %(message)s") + # <p>Hello, World! Traceback (most recent call last): + # <ul> + # <li>Hello, World! File "/Users/aaron/work/projects/zope.exceptions-traceback-log-formatting/src/zope/exceptions/tests/test_exceptionformatter.py", line 197, in testFormattedExceptionHTML<br /> + # Hello, World! exec 'syntax error'</li> + # </ul>Hello, World! File "<string>", line 1<br /> + # Hello, World! syntax error<br /> + # Hello, World! ^<br /> + # Hello, World! SyntaxError: unexpected EOF while parsing<br /> + # </p> + self.assertEquals(s.splitlines()[-4:], + ['Hello, World! syntax error<br />', + 'Hello, World! ^<br />', + 'Hello, World! SyntaxError: unexpected EOF while parsing<br />', + '</p>']) def test_suite(): - return makeSuite(Test) + return unittest.TestSuite([ + unittest.makeSuite(Test), + ]) if __name__=='__main__': - main(defaultTest='test_suite') + unittest.main(defaultTest='test_suite') |
