← Back to team overview

launchpad-reviewers team mailing list archive

[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