Merge lp:~lifeless/python-oops-datedir-repo/extraction into lp:python-oops-datedir-repo

Proposed by Robert Collins
Status: Merged
Merged at revision: 7
Proposed branch: lp:~lifeless/python-oops-datedir-repo/extraction
Merge into: lp:python-oops-datedir-repo
Diff against target: 327 lines (+114/-45)
4 files modified
oops_datedir_repo/__init__.py (+1/-1)
oops_datedir_repo/serializer_rfc822.py (+43/-33)
oops_datedir_repo/tests/test_serializer_rfc822.py (+69/-10)
setup.py (+1/-1)
To merge this branch: bzr merge lp:~lifeless/python-oops-datedir-repo/extraction
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+71795@code.launchpad.net

Description of the change

This handles the new topic and reporter keys (mapping pageid to topic on in-out for compat with oops-tools (until oops-tools uses this library :P)), and takes more encoding responsibility so that callers can relax a little (given the definition we're aiming at of 'bson serializable is all we need).

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'oops_datedir_repo/__init__.py'
--- oops_datedir_repo/__init__.py 2011-08-16 02:21:20 +0000
+++ oops_datedir_repo/__init__.py 2011-08-17 01:17:24 +0000
@@ -25,7 +25,7 @@
25# established at this point, and setup.py will use a version of next-$(revno).25# established at this point, and setup.py will use a version of next-$(revno).
26# If the releaselevel is 'final', then the tarball will be major.minor.micro.26# If the releaselevel is 'final', then the tarball will be major.minor.micro.
27# Otherwise it is major.minor.micro~$(revno).27# Otherwise it is major.minor.micro~$(revno).
28__version__ = (0, 0, 2, 'beta', 0)28__version__ = (0, 0, 3, 'beta', 0)
2929
30__all__ = [30__all__ = [
31 'DateDirRepo',31 'DateDirRepo',
3232
=== modified file 'oops_datedir_repo/serializer_rfc822.py'
--- oops_datedir_repo/serializer_rfc822.py 2011-08-15 05:30:39 +0000
+++ oops_datedir_repo/serializer_rfc822.py 2011-08-17 01:17:24 +0000
@@ -19,13 +19,16 @@
19This style of OOPS format is very web server specific, not extensible - it19This style of OOPS format is very web server specific, not extensible - it
20should be considered deprecated.20should be considered deprecated.
2121
22The reports this serializer handles always have the following variables:22The reports this serializer handles always have the following variables (See
23the python-oops api docs for more information about these variables):
2324
24* id: The name of this error report.25* id: The name of this error report.
25* type: The type of the exception that occurred.26* type: The type of the exception that occurred.
26* value: The value of the exception that occurred.27* value: The value of the exception that occurred.
27* time: The time at which the exception occurred.28* time: The time at which the exception occurred.
28* pageid: The identifier for the template/script that oopsed.29* reporter: The reporting program.
30* topic: The identifier for the template/script that oopsed.
31 [this is written as Page-Id for compatibility with as yet unported tools.]
29* branch_nick: The branch nickname.32* branch_nick: The branch nickname.
30* revno: The revision number of the branch.33* revno: The revision number of the branch.
31* tb_text: A text version of the traceback.34* tb_text: A text version of the traceback.
@@ -37,6 +40,9 @@
37* revno: The revision that the branch was at.40* revno: The revision that the branch was at.
38* Informational: A flag, True if the error wasn't fatal- if it was41* Informational: A flag, True if the error wasn't fatal- if it was
39 'informational'.42 'informational'.
43 [Deprecated - this is no longer part of the oops report conventions. Existing
44 reports with it set are still read, but the key is only present if it was
45 truely in the report.]
40"""46"""
4147
4248
@@ -67,13 +73,16 @@
67 date =iso8601.parse_date(msg.getheader('date'))73 date =iso8601.parse_date(msg.getheader('date'))
68 else:74 else:
69 date = None75 date = None
70 pageid = msg.getheader('page-id')76 topic = msg.getheader('topic')
77 if topic is None:
78 topic = msg.getheader('page-id')
71 username = msg.getheader('user')79 username = msg.getheader('user')
72 url = msg.getheader('url')80 url = msg.getheader('url')
73 duration = int(float(msg.getheader('duration', '-1')))81 duration = int(float(msg.getheader('duration', '-1')))
74 informational = msg.getheader('informational')82 informational = msg.getheader('informational')
75 branch_nick = msg.getheader('branch')83 branch_nick = msg.getheader('branch')
76 revno = msg.getheader('revision')84 revno = msg.getheader('revision')
85 reporter = msg.getheader('oops-reporter')
7786
78 # Explicitly use an iterator so we can process the file87 # Explicitly use an iterator so we can process the file
79 # sequentially. In most instances the iterator will actually88 # sequentially. In most instances the iterator will actually
@@ -109,10 +118,15 @@
109 # The rest is traceback.118 # The rest is traceback.
110 tb_text = ''.join(lines)119 tb_text = ''.join(lines)
111120
112 return dict(id=id, type=exc_type, value=exc_value, time=date,121 result = dict(id=id, type=exc_type, value=exc_value, time=date,
113 pageid=pageid, tb_text=tb_text, username=username, url=url,122 topic=topic, tb_text=tb_text, username=username, url=url,
114 duration=duration, req_vars=req_vars, db_statements=statements,123 duration=duration, req_vars=req_vars, db_statements=statements,
115 informational=informational, branch_nick=branch_nick, revno=revno)124 branch_nick=branch_nick, revno=revno)
125 if informational is not None:
126 result['informational'] = informational
127 if reporter is not None:
128 result['reporter'] = reporter
129 return result
116130
117131
118def _normalise_whitespace(s):132def _normalise_whitespace(s):
@@ -151,45 +165,41 @@
151def to_chunks(report):165def to_chunks(report):
152 """Returns a list of bytestrings making up the serialized oops."""166 """Returns a list of bytestrings making up the serialized oops."""
153 chunks = []167 chunks = []
154 chunks.append('Oops-Id: %s\n' % _normalise_whitespace(report['id']))168 def header(label, key, optional=True):
155 if 'type' in report:169 if optional and key not in report:
156 chunks.append(170 return
157 'Exception-Type: %s\n' % _normalise_whitespace(report['type']))171 value = _safestr(report[key])
158 if 'value' in report:172 value = _normalise_whitespace(value)
159 chunks.append(173 chunks.append('%s: %s\n' % (label, value))
160 'Exception-Value: %s\n' % _normalise_whitespace(report['value']))174 header('Oops-Id', 'id', optional=False)
175 header('Exception-Type', 'type')
176 header('Exception-Value', 'value')
161 if 'time' in report:177 if 'time' in report:
162 chunks.append('Date: %s\n' % report['time'].isoformat())178 chunks.append('Date: %s\n' % report['time'].isoformat())
163 if 'pageid' in report:179 header('Page-Id', 'topic')
164 chunks.append(180 header('Branch', 'branch_nick')
165 'Page-Id: %s\n' % _normalise_whitespace(report['pageid']))181 header('Revision', 'revno')
166 if 'branch_nick' in report:182 header('User', 'username')
167 chunks.append('Branch: %s\n' % _safestr(report['branch_nick']))183 header('URL', 'url')
168 if 'revno' in report:184 header('Duration', 'duration')
169 chunks.append('Revision: %s\n' % report['revno'])185 header('Informational', 'informational')
170 if 'username' in report:186 header('Oops-Reporter', 'reporter')
171 chunks.append(
172 'User: %s\n' % _normalise_whitespace(report['username']))
173 if 'url' in report:
174 chunks.append('URL: %s\n' % _normalise_whitespace(report['url']))
175 if 'duration' in report:
176 chunks.append('Duration: %s\n' % report['duration'])
177 if 'informational' in report:
178 chunks.append('Informational: %s\n' % report['informational'])
179 chunks.append('\n')187 chunks.append('\n')
180 safe_chars = ';/\\?:@&+$, ()*!'188 safe_chars = ';/\\?:@&+$, ()*!'
181 if 'req_vars' in report:189 if 'req_vars' in report:
182 for key, value in report['req_vars']:190 for key, value in report['req_vars']:
183 chunks.append('%s=%s\n' % (urllib.quote(key, safe_chars),191 chunks.append('%s=%s\n' % (
184 urllib.quote(value, safe_chars)))192 urllib.quote(_safestr(key), safe_chars),
193 urllib.quote(_safestr(value), safe_chars)))
185 chunks.append('\n')194 chunks.append('\n')
186 if 'db_statements' in report:195 if 'db_statements' in report:
187 for (start, end, database_id, statement) in report['db_statements']:196 for (start, end, database_id, statement) in report['db_statements']:
188 chunks.append('%05d-%05d@%s %s\n' % (197 chunks.append('%05d-%05d@%s %s\n' % (
189 start, end, database_id, _normalise_whitespace(statement)))198 start, end, _safestr(database_id),
199 _safestr(_normalise_whitespace(statement))))
190 chunks.append('\n')200 chunks.append('\n')
191 if 'tb_text' in report:201 if 'tb_text' in report:
192 chunks.append(report['tb_text'])202 chunks.append(_safestr(report['tb_text']))
193 return chunks203 return chunks
194204
195205
196206
=== modified file 'oops_datedir_repo/tests/test_serializer_rfc822.py'
--- oops_datedir_repo/tests/test_serializer_rfc822.py 2011-08-15 05:30:39 +0000
+++ oops_datedir_repo/tests/test_serializer_rfc822.py 2011-08-17 01:17:24 +0000
@@ -41,7 +41,7 @@
41 Exception-Type: NotFound41 Exception-Type: NotFound
42 Exception-Value: error message42 Exception-Value: error message
43 Date: 2005-04-01T00:00:00+00:0043 Date: 2005-04-01T00:00:00+00:00
44 Page-Id: IFoo:+foo-template44 Topic: IFoo:+foo-template
45 User: Sample User45 User: Sample User
46 URL: http://localhost:9000/foo46 URL: http://localhost:9000/foo
47 Duration: 4247 Duration: 42
@@ -60,7 +60,7 @@
60 self.assertEqual(report['value'], 'error message')60 self.assertEqual(report['value'], 'error message')
61 self.assertEqual(61 self.assertEqual(
62 report['time'], datetime.datetime(2005, 4, 1, tzinfo=utc))62 report['time'], datetime.datetime(2005, 4, 1, tzinfo=utc))
63 self.assertEqual(report['pageid'], 'IFoo:+foo-template')63 self.assertEqual(report['topic'], 'IFoo:+foo-template')
64 self.assertEqual(report['tb_text'], 'traceback-text')64 self.assertEqual(report['tb_text'], 'traceback-text')
65 self.assertEqual(report['username'], 'Sample User')65 self.assertEqual(report['username'], 'Sample User')
66 self.assertEqual(report['url'], 'http://localhost:9000/foo')66 self.assertEqual(report['url'], 'http://localhost:9000/foo')
@@ -86,7 +86,7 @@
86 Exception-Type: NotFound86 Exception-Type: NotFound
87 Exception-Value: error message87 Exception-Value: error message
88 Date: 2005-04-01T00:00:00+00:0088 Date: 2005-04-01T00:00:00+00:00
89 Page-Id: IFoo:+foo-template89 Topic: IFoo:+foo-template
90 User: Sample User90 User: Sample User
91 URL: http://localhost:9000/foo91 URL: http://localhost:9000/foo
92 Duration: 4292 Duration: 42
@@ -105,7 +105,7 @@
105 self.assertEqual(report['value'], 'error message')105 self.assertEqual(report['value'], 'error message')
106 self.assertEqual(106 self.assertEqual(
107 report['time'], datetime.datetime(2005, 4, 1, tzinfo=utc))107 report['time'], datetime.datetime(2005, 4, 1, tzinfo=utc))
108 self.assertEqual(report['pageid'], 'IFoo:+foo-template')108 self.assertEqual(report['topic'], 'IFoo:+foo-template')
109 self.assertEqual(report['tb_text'], 'traceback-text')109 self.assertEqual(report['tb_text'], 'traceback-text')
110 self.assertEqual(report['username'], 'Sample User')110 self.assertEqual(report['username'], 'Sample User')
111 self.assertEqual(report['url'], 'http://localhost:9000/foo')111 self.assertEqual(report['url'], 'http://localhost:9000/foo')
@@ -119,7 +119,6 @@
119 self.assertEqual(len(report['db_statements']), 2)119 self.assertEqual(len(report['db_statements']), 2)
120 self.assertEqual(report['db_statements'][0], (1, 5, None, 'SELECT 1'))120 self.assertEqual(report['db_statements'][0], (1, 5, None, 'SELECT 1'))
121 self.assertEqual(report['db_statements'][1], (5, 10, None, 'SELECT 2'))121 self.assertEqual(report['db_statements'][1], (5, 10, None, 'SELECT 2'))
122 self.assertFalse(report['informational'])
123122
124 def test_read_branch_nick_revno(self):123 def test_read_branch_nick_revno(self):
125 """Test ErrorReport.read()."""124 """Test ErrorReport.read()."""
@@ -128,7 +127,6 @@
128 Exception-Type: NotFound127 Exception-Type: NotFound
129 Exception-Value: error message128 Exception-Value: error message
130 Date: 2005-04-01T00:00:00+00:00129 Date: 2005-04-01T00:00:00+00:00
131 Page-Id: IFoo:+foo-template
132 User: Sample User130 User: Sample User
133 URL: http://localhost:9000/foo131 URL: http://localhost:9000/foo
134 Duration: 42132 Duration: 42
@@ -147,6 +145,45 @@
147 self.assertEqual(report['branch_nick'], 'mybranch')145 self.assertEqual(report['branch_nick'], 'mybranch')
148 self.assertEqual(report['revno'], '45')146 self.assertEqual(report['revno'], '45')
149147
148 def test_read_reporter(self):
149 """Test ErrorReport.read()."""
150 fp = StringIO.StringIO(dedent("""\
151 Oops-Id: OOPS-A0001
152 Oops-Reporter: foo/bar
153
154 """))
155 report = read(fp)
156 self.assertEqual(report['reporter'], 'foo/bar')
157
158 def test_read_pageid_to_topic(self):
159 """Test ErrorReport.read()."""
160 fp = StringIO.StringIO(dedent("""\
161 Oops-Id: OOPS-A0001
162 Page-Id: IFoo:+foo-template
163
164 """))
165 report = read(fp)
166 self.assertEqual(report['topic'], 'IFoo:+foo-template')
167
168 def test_read_informational_read(self):
169 """Test ErrorReport.read()."""
170 fp = StringIO.StringIO(dedent("""\
171 Oops-Id: OOPS-A0001
172 Informational: True
173
174 """))
175 report = read(fp)
176 self.assertEqual('True', report['informational'])
177
178 def test_read_no_informational_no_key(self):
179 """Test ErrorReport.read()."""
180 fp = StringIO.StringIO(dedent("""\
181 Oops-Id: OOPS-A0001
182
183 """))
184 report = read(fp)
185 self.assertFalse('informational' in report)
186
150 def test_minimal_oops(self):187 def test_minimal_oops(self):
151 # If we get a crazy-small oops, we can read it sensibly. Because there188 # If we get a crazy-small oops, we can read it sensibly. Because there
152 # is existing legacy code, all keys are filled in with None, [] rather189 # is existing legacy code, all keys are filled in with None, [] rather
@@ -159,14 +196,13 @@
159 self.assertEqual(report['type'], None)196 self.assertEqual(report['type'], None)
160 self.assertEqual(report['value'], None)197 self.assertEqual(report['value'], None)
161 self.assertEqual(report['time'], None)198 self.assertEqual(report['time'], None)
162 self.assertEqual(report['pageid'], None)199 self.assertEqual(report['topic'], None)
163 self.assertEqual(report['tb_text'], '')200 self.assertEqual(report['tb_text'], '')
164 self.assertEqual(report['username'], None)201 self.assertEqual(report['username'], None)
165 self.assertEqual(report['url'], None)202 self.assertEqual(report['url'], None)
166 self.assertEqual(report['duration'], -1)203 self.assertEqual(report['duration'], -1)
167 self.assertEqual(len(report['req_vars']), 0)204 self.assertEqual(len(report['req_vars']), 0)
168 self.assertEqual(len(report['db_statements']), 0)205 self.assertEqual(len(report['db_statements']), 0)
169 self.assertFalse(report['informational'])
170 self.assertEqual(report['branch_nick'], None)206 self.assertEqual(report['branch_nick'], None)
171 self.assertEqual(report['revno'], None)207 self.assertEqual(report['revno'], None)
172208
@@ -180,7 +216,7 @@
180 'type': 'NotFound',216 'type': 'NotFound',
181 'value': 'error message',217 'value': 'error message',
182 'time': datetime.datetime(2005, 04, 01, 00, 00, 00, tzinfo=utc),218 'time': datetime.datetime(2005, 04, 01, 00, 00, 00, tzinfo=utc),
183 'pageid': 'IFoo:+foo-template',219 'topic': 'IFoo:+foo-template',
184 'tb_text': 'traceback-text',220 'tb_text': 'traceback-text',
185 'username': 'Sample User',221 'username': 'Sample User',
186 'url': 'http://localhost:9000/foo',222 'url': 'http://localhost:9000/foo',
@@ -223,7 +259,7 @@
223 'type': 'NotFound',259 'type': 'NotFound',
224 'value': 'error message',260 'value': 'error message',
225 'time': datetime.datetime(2005, 04, 01, 00, 00, 00, tzinfo=utc),261 'time': datetime.datetime(2005, 04, 01, 00, 00, 00, tzinfo=utc),
226 'pageid': 'IFoo:+foo-template',262 'topic': 'IFoo:+foo-template',
227 'tb_text': 'traceback-text',263 'tb_text': 'traceback-text',
228 'username': 'Sample User',264 'username': 'Sample User',
229 'url': 'http://localhost:9000/foo',265 'url': 'http://localhost:9000/foo',
@@ -269,3 +305,26 @@
269 "Oops-Id: OOPS-1234\n",305 "Oops-Id: OOPS-1234\n",
270 "\n"306 "\n"
271 ], to_chunks(report))307 ], to_chunks(report))
308
309 def test_reporter(self):
310 report = {'reporter': 'foo', 'id': 'bar'}
311 self.assertEqual([
312 "Oops-Id: bar\n",
313 "Oops-Reporter: foo\n",
314 "\n",
315 ], to_chunks(report))
316
317 def test_bad_strings(self):
318 # Because of the rfc822 limitations, not all strings can be supported
319 # in this format - particularly in headers... so all header strings are
320 # passed through an escape process.
321 report = {'id': u'\xeafoo'}
322 self.assertEqual([
323 "Oops-Id: \\xeafoo\n",
324 "\n",
325 ], to_chunks(report))
326 report = {'id': '\xeafoo'}
327 self.assertEqual([
328 "Oops-Id: \\xeafoo\n",
329 "\n",
330 ], to_chunks(report))
272331
=== modified file 'setup.py'
--- setup.py 2011-08-16 02:21:20 +0000
+++ setup.py 2011-08-17 01:17:24 +0000
@@ -22,7 +22,7 @@
22description = file(os.path.join(os.path.dirname(__file__), 'README'), 'rb').read()22description = file(os.path.join(os.path.dirname(__file__), 'README'), 'rb').read()
2323
24setup(name="oops_datedir_repo",24setup(name="oops_datedir_repo",
25 version="0.0.2",25 version="0.0.3",
26 description="OOPS disk serialisation and repository management.",26 description="OOPS disk serialisation and repository management.",
27 long_description=description,27 long_description=description,
28 maintainer="Launchpad Developers",28 maintainer="Launchpad Developers",

Subscribers

People subscribed via source and target branches

to all changes: