launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04561
[Merge] lp:~lifeless/python-oops/extraction into lp:python-oops
Robert Collins has proposed merging lp:~lifeless/python-oops/extraction into lp:python-oops.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~lifeless/python-oops/extraction/+merge/71128
More code from LP - the serialisation bits this time.
They are -clearly- clunky in a more flexible world, but I have not tried to improve them at all at this point (other than making an explicit test for to_chunks).
That can wait for an actual use case :).
--
https://code.launchpad.net/~lifeless/python-oops/extraction/+merge/71128
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/python-oops/extraction into lp:python-oops.
=== modified file 'oops/serializer_rfc822.py'
--- oops/serializer_rfc822.py 2011-08-10 06:44:48 +0000
+++ oops/serializer_rfc822.py 2011-08-11 00:26:24 +0000
@@ -19,7 +19,7 @@
This style of OOPS format is very web server specific, not extensible - it
should be considered deprecated.
-It always has the following variables:
+The reports this serializer handles always have the following variables:
* id: The name of this error report.
* type: The type of the exception that occurred.
@@ -32,14 +32,23 @@
* username: The user associated with the request.
* url: The URL for the failed request.
* req_vars: The request variables.
+* branch_nick: A name for the branch of code that was running when the report
+ was triggered.
+* revno: The revision that the branch was at.
+* Informational: A flag, True if the error wasn't fatal- if it was
+ 'informational'.
"""
-__all__ = ['read']
+__all__ = [
+ 'read',
+ 'write',
+ ]
__metaclass__ = type
import datetime
+import logging
import rfc822
import re
import urllib
@@ -98,3 +107,71 @@
pageid=pageid, tb_text=tb_text, username=username, url=url,
duration=duration, req_vars=req_vars, db_statements=statements,
informational=informational)
+
+
+def _normalise_whitespace(s):
+ """Normalise the whitespace in a string to spaces"""
+ if s is None:
+ return None # (used by the cast to %s to get 'None')
+ return ' '.join(s.split())
+
+
+def _safestr(obj):
+ if isinstance(obj, unicode):
+ return obj.replace('\\', '\\\\').encode('ASCII',
+ 'backslashreplace')
+ # A call to str(obj) could raise anything at all.
+ # We'll ignore these errors, and print something
+ # useful instead, but also log the error.
+ # We disable the pylint warning for the blank except.
+ try:
+ value = str(obj)
+ except:
+ logging.getLogger('oops.serializer_rfc822').exception(
+ 'Error while getting a str '
+ 'representation of an object')
+ value = '<unprintable %s object>' % (
+ str(type(obj).__name__))
+ # Some str() calls return unicode objects.
+ if isinstance(value, unicode):
+ return _safestr(value)
+ # encode non-ASCII characters
+ value = value.replace('\\', '\\\\')
+ value = re.sub(r'[\x80-\xff]',
+ lambda match: '\\x%02x' % ord(match.group(0)), value)
+ return value
+
+
+def to_chunks(report):
+ """Returns a list of bytestrings making up the serialized oops."""
+ chunks = []
+ chunks.append('Oops-Id: %s\n' % _normalise_whitespace(report['id']))
+ chunks.append(
+ 'Exception-Type: %s\n' % _normalise_whitespace(report['type']))
+ chunks.append(
+ 'Exception-Value: %s\n' % _normalise_whitespace(report['value']))
+ chunks.append('Date: %s\n' % report['time'].isoformat())
+ chunks.append('Page-Id: %s\n' % _normalise_whitespace(report['pageid']))
+ chunks.append('Branch: %s\n' % _safestr(report['branch_nick']))
+ chunks.append('Revision: %s\n' % report['revno'])
+ chunks.append('User: %s\n' % _normalise_whitespace(report['username']))
+ chunks.append('URL: %s\n' % _normalise_whitespace(report['url']))
+ chunks.append('Duration: %s\n' % report['duration'])
+ chunks.append('Informational: %s\n' % report['informational'])
+ chunks.append('\n')
+ safe_chars = ';/\\?:@&+$, ()*!'
+ for key, value in report['req_vars']:
+ chunks.append('%s=%s\n' % (urllib.quote(key, safe_chars),
+ urllib.quote(value, safe_chars)))
+ chunks.append('\n')
+ for (start, end, database_id, statement) in report['db_statements']:
+ chunks.append('%05d-%05d@%s %s\n' % (
+ start, end, database_id, _normalise_whitespace(statement)))
+ chunks.append('\n')
+ chunks.append(report['tb_text'])
+ return chunks
+
+
+def write(report, output):
+ """Write a report to a file."""
+ output.writelines(to_chunks(report))
=== modified file 'oops/tests/test_serializer_rfc822.py'
--- oops/tests/test_serializer_rfc822.py 2011-08-10 06:08:53 +0000
+++ oops/tests/test_serializer_rfc822.py 2011-08-11 00:26:24 +0000
@@ -25,7 +25,11 @@
from pytz import utc
import testtools
-from oops.serializer_rfc822 import read
+from oops.serializer_rfc822 import (
+ read,
+ to_chunks,
+ write,
+ )
class TestParsing(testtools.TestCase):
@@ -115,3 +119,95 @@
self.assertEqual(len(report['db_statements']), 2)
self.assertEqual(report['db_statements'][0], (1, 5, None, 'SELECT 1'))
self.assertEqual(report['db_statements'][1], (5, 10, None, 'SELECT 2'))
+ self.assertFalse(report['informational'])
+
+
+class TestSerializing(testtools.TestCase):
+
+ def test_write_file(self):
+ output = StringIO.StringIO()
+ report = {
+ 'id': 'OOPS-A0001',
+ 'type': 'NotFound',
+ 'value': 'error message',
+ 'time': datetime.datetime(2005, 04, 01, 00, 00, 00, tzinfo=utc),
+ 'pageid': 'IFoo:+foo-template',
+ 'tb_text': 'traceback-text',
+ 'username': 'Sample User',
+ 'url': 'http://localhost:9000/foo',
+ 'duration': 42,
+ 'req_vars': [('HTTP_USER_AGENT', 'Mozilla/5.0'),
+ ('HTTP_REFERER', 'http://localhost:9000/'),
+ ('name=foo', 'hello\nworld')],
+ 'db_statements': [(1, 5, 'store_a', 'SELECT 1'),
+ (5, 10, 'store_b', 'SELECT\n2')],
+ 'informational': False,
+ 'branch_nick': 'mybranch',
+ 'revno': '45',
+ }
+ write(report, output)
+ self.assertEqual(output.getvalue(), dedent("""\
+ Oops-Id: OOPS-A0001
+ Exception-Type: NotFound
+ Exception-Value: error message
+ Date: 2005-04-01T00:00:00+00:00
+ Page-Id: IFoo:+foo-template
+ Branch: mybranch
+ Revision: 45
+ User: Sample User
+ URL: http://localhost:9000/foo
+ Duration: 42
+ Informational: False
+
+ HTTP_USER_AGENT=Mozilla/5.0
+ HTTP_REFERER=http://localhost:9000/
+ name%3Dfoo=hello%0Aworld
+
+ 00001-00005@store_a SELECT 1
+ 00005-00010@store_b SELECT 2
+
+ traceback-text"""))
+
+ def test_to_chunks(self):
+ report = {
+ 'id': 'OOPS-A0001',
+ 'type': 'NotFound',
+ 'value': 'error message',
+ 'time': datetime.datetime(2005, 04, 01, 00, 00, 00, tzinfo=utc),
+ 'pageid': 'IFoo:+foo-template',
+ 'tb_text': 'traceback-text',
+ 'username': 'Sample User',
+ 'url': 'http://localhost:9000/foo',
+ 'duration': 42,
+ 'req_vars': [('HTTP_USER_AGENT', 'Mozilla/5.0'),
+ ('HTTP_REFERER', 'http://localhost:9000/'),
+ ('name=foo', 'hello\nworld')],
+ 'db_statements': [(1, 5, 'store_a', 'SELECT 1'),
+ (5, 10, 'store_b', 'SELECT\n2')],
+ 'informational': False,
+ 'branch_nick': 'mybranch',
+ 'revno': '45',
+ }
+ self.assertEqual([
+ "Oops-Id: OOPS-A0001\n",
+ "Exception-Type: NotFound\n",
+ "Exception-Value: error message\n",
+ "Date: 2005-04-01T00:00:00+00:00\n",
+ "Page-Id: IFoo:+foo-template\n",
+ "Branch: mybranch\n",
+ "Revision: 45\n",
+ "User: Sample User\n",
+ "URL: http://localhost:9000/foo\n",
+ "Duration: 42\n",
+ "Informational: False\n",
+ "\n",
+ "HTTP_USER_AGENT=Mozilla/5.0\n",
+ "HTTP_REFERER=http://localhost:9000/\n",
+ "name%3Dfoo=hello%0Aworld\n",
+ "\n",
+ "00001-00005@store_a SELECT 1\n",
+ "00005-00010@store_b SELECT 2\n",
+ "\n",
+ "traceback-text",
+ ],
+ to_chunks(report))
Follow ups