launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02551
[Merge] lp:~allenap/launchpad/use-zope-tb-formatter into lp:launchpad
Gavin Panella has proposed merging lp:~allenap/launchpad/use-zope-tb-formatter into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#714820 Many ExpatErrors from checkwatches
https://bugs.launchpad.net/bugs/714820
For more details, see:
https://code.launchpad.net/~allenap/launchpad/use-zope-tb-formatter/+merge/48843
This branch does two things:
- Adds a new function, traceback_info(), that injects a
__traceback_info__ local variable into the calling frame. From
its docstring:
This is more aesthetically pleasing that assigning to
__traceback_info__, but it more importantly avoids spurious
lint warnings about unused local variables, and helps to
avoid typos.
- Uses zope.exceptions to format logged exceptions, which means that
the traceback info will be included.
The OOPS tools already use zope.exceptions to format exceptions so
this extra info should show up in OOPS reports soon.
--
https://code.launchpad.net/~allenap/launchpad/use-zope-tb-formatter/+merge/48843
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/use-zope-tb-formatter into lp:launchpad.
=== modified file 'lib/canonical/launchpad/scripts/logger.py'
--- lib/canonical/launchpad/scripts/logger.py 2010-12-20 20:36:16 +0000
+++ lib/canonical/launchpad/scripts/logger.py 2011-02-08 21:21:56 +0000
@@ -15,13 +15,19 @@
# Don't import stuff from this module. Import it from canonical.scripts
__all__ = [
+ 'DEBUG2',
+ 'DEBUG3',
+ 'DEBUG4',
+ 'DEBUG5',
+ 'DEBUG6',
+ 'DEBUG7',
+ 'DEBUG8',
+ 'DEBUG9',
+ 'LaunchpadFormatter',
'log',
'logger',
'logger_options',
'OopsHandler',
- 'LaunchpadFormatter',
- 'DEBUG2', 'DEBUG3', 'DEBUG4', 'DEBUG5',
- 'DEBUG6', 'DEBUG7', 'DEBUG8', 'DEBUG9',
]
@@ -38,10 +44,11 @@
import re
import sys
import time
-import traceback
+from traceback import format_exception_only
from pytz import utc
from zope.component import getUtility
+from zope.exceptions.log import Formatter
from canonical.base import base
from canonical.config import config
@@ -55,7 +62,6 @@
)
from lp.services.log import loglevels
-
# Reexport our custom loglevels for old callsites. These callsites
# should be importing the symbols from lp.services.log.loglevels
DEBUG2 = loglevels.DEBUG2
@@ -91,7 +97,7 @@
self.handleError(record)
-class LaunchpadFormatter(logging.Formatter):
+class LaunchpadFormatter(Formatter):
"""logging.Formatter encoding our preferred output format."""
def __init__(self, fmt=None, datefmt=None):
@@ -124,7 +130,7 @@
Returns the URL, or the formatted exception if the Librarian is
not available.
"""
- traceback = logging.Formatter.formatException(self, ei)
+ traceback = LaunchpadFormatter.formatException(self, ei)
# Uncomment this line to stop exception storage in the librarian.
# Useful for debugging tests.
# return traceback
@@ -158,7 +164,7 @@
# information, we can stuff our own problems in there too.
return '%s\n\nException raised in formatter:\n%s\n' % (
traceback,
- logging.Formatter.formatException(self, sys.exc_info()))
+ LaunchpadFormatter.formatException(self, sys.exc_info()))
def logger_options(parser, default=logging.INFO):
@@ -408,7 +414,7 @@
def shortException(self, msg, *args):
"""Like Logger.exception, but does not print a traceback."""
exctype, value = sys.exc_info()[:2]
- report = ''.join(traceback.format_exception_only(exctype, value))
+ report = ''.join(format_exception_only(exctype, value))
# _log.error interpolates msg, so we need to escape % chars
msg += '\n' + report.rstrip('\n').replace('%', '%%')
self._log.error(msg, *args)
=== modified file 'lib/canonical/launchpad/scripts/tests/test_logger.py'
--- lib/canonical/launchpad/scripts/tests/test_logger.py 2010-10-04 19:50:45 +0000
+++ lib/canonical/launchpad/scripts/tests/test_logger.py 2011-02-08 21:21:56 +0000
@@ -6,13 +6,55 @@
__metaclass__ = type
__all__ = []
+import doctest
+from sys import exc_info
import unittest
+from testtools.matchers import DocTestMatches
+
+from canonical.launchpad.scripts.logger import LaunchpadFormatter
from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite
from canonical.testing.layers import BaseLayer
+from lp.services.utils import traceback_info
+from lp.testing import TestCase
+
+
+DOCTEST_FLAGS = (
+ doctest.ELLIPSIS |
+ doctest.NORMALIZE_WHITESPACE |
+ doctest.REPORT_NDIFF)
+
+
+class TestLaunchpadFormatter(TestCase):
+ """Tests of `LaunchpadFormatter`."""
+
+ def test_traceback_info(self):
+ # LaunchpadFormatter inherits from zope.exceptions.log.Formatter, so
+ # __traceback_info__ annotations are included in formatted exceptions.
+
+ traceback_info("Captain Kirk")
+
+ try:
+ 0/0
+ except ZeroDivisionError:
+ info = exc_info()
+
+ self.assertThat(
+ LaunchpadFormatter().formatException(info),
+ DocTestMatches(
+ flags=DOCTEST_FLAGS, example="""
+ Traceback (most recent call last):
+ ...
+ __traceback_info__: Captain Kirk
+ ZeroDivisionError: ...
+ """))
def test_suite():
- return unittest.TestSuite([
+ suite = unittest.TestSuite()
+ suite.addTest(
LayeredDocFileSuite(
- 'test_logger.txt', layer=BaseLayer),])
+ 'test_logger.txt', layer=BaseLayer))
+ suite.addTest(
+ unittest.TestLoader().loadTestsFromName(__name__))
+ return suite
=== modified file 'lib/lp/services/tests/test_utils.py'
--- lib/lp/services/tests/test_utils.py 2011-01-26 22:07:08 +0000
+++ lib/lp/services/tests/test_utils.py 2011-02-08 21:21:56 +0000
@@ -14,6 +14,7 @@
decorate_with,
docstring_dedent,
iter_split,
+ traceback_info,
)
from lp.testing import TestCase
@@ -42,35 +43,35 @@
# The same iterator can be used multiple times.
iterator = CachingIterator(itertools.count())
self.assertEqual(
- [0,1,2,3,4], list(itertools.islice(iterator, 0, 5)))
+ [0, 1, 2, 3, 4], list(itertools.islice(iterator, 0, 5)))
self.assertEqual(
- [0,1,2,3,4], list(itertools.islice(iterator, 0, 5)))
+ [0, 1, 2, 3, 4], list(itertools.islice(iterator, 0, 5)))
def test_more_values(self):
# If a subsequent call to iter causes more values to be fetched, they
# are also cached.
iterator = CachingIterator(itertools.count())
self.assertEqual(
- [0,1,2], list(itertools.islice(iterator, 0, 3)))
+ [0, 1, 2], list(itertools.islice(iterator, 0, 3)))
self.assertEqual(
- [0,1,2,3,4], list(itertools.islice(iterator, 0, 5)))
+ [0, 1, 2, 3, 4], list(itertools.islice(iterator, 0, 5)))
def test_limited_iterator(self):
# Make sure that StopIteration is handled correctly.
- iterator = CachingIterator(iter([0,1,2,3,4]))
+ iterator = CachingIterator(iter([0, 1, 2, 3, 4]))
self.assertEqual(
- [0,1,2], list(itertools.islice(iterator, 0, 3)))
- self.assertEqual([0,1,2,3,4], list(iterator))
+ [0, 1, 2], list(itertools.islice(iterator, 0, 3)))
+ self.assertEqual([0, 1, 2, 3, 4], list(iterator))
def test_parallel_iteration(self):
# There can be parallel iterators over the CachingIterator.
- ci = CachingIterator(iter([0,1,2,3,4]))
+ ci = CachingIterator(iter([0, 1, 2, 3, 4]))
i1 = iter(ci)
i2 = iter(ci)
self.assertEqual(0, i1.next())
self.assertEqual(0, i2.next())
- self.assertEqual([1,2,3,4], list(i2))
- self.assertEqual([1,2,3,4], list(i1))
+ self.assertEqual([1, 2, 3, 4], list(i2))
+ self.assertEqual([1, 2, 3, 4], list(i1))
class TestDecorateWith(TestCase):
@@ -155,5 +156,16 @@
result = 'This is a multiline docstring.\n\nThis is the second line.'
self.assertEqual(docstring_dedent(docstring), result)
+
+class TestTracebackInfo(TestCase):
+ """Tests of `traceback_info`."""
+
+ def test(self):
+ # The local variable __traceback_info__ is set by `traceback_info`.
+ self.assertEqual(None, locals().get("__traceback_info__"))
+ traceback_info("Pugwash")
+ self.assertEqual("Pugwash", locals().get("__traceback_info__"))
+
+
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
=== modified file 'lib/lp/services/utils.py'
--- lib/lp/services/utils.py 2011-02-08 15:08:01 +0000
+++ lib/lp/services/utils.py 2011-02-08 21:21:56 +0000
@@ -15,10 +15,12 @@
'iter_split',
'synchronize',
'text_delta',
+ 'traceback_info',
'value_string',
]
from itertools import tee
+import sys
from textwrap import dedent
from lazr.enum import BaseItem
@@ -150,3 +152,13 @@
# Make sure there is at least one newline so the split works.
first, rest = (s+'\n').split('\n', 1)
return (first + '\n' + dedent(rest)).strip()
+
+
+def traceback_info(info):
+ """Set `__traceback_info__` in the caller's locals.
+
+ This is more aesthetically pleasing that assigning to __traceback_info__,
+ but it more importantly avoids spurious lint warnings about unused local
+ variables, and helps to avoid typos.
+ """
+ sys._getframe(1).f_locals["__traceback_info__"] = info