Merge lp:~matsubara/launchpad/bug-436640 into lp:launchpad/db-devel

Proposed by Diogo Matsubara
Status: Merged
Approved by: Diogo Matsubara
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~matsubara/launchpad/bug-436640
Merge into: lp:launchpad/db-devel
Diff against target: 372 lines
6 files modified
lib/canonical/launchpad/webapp/errorlog.py (+17/-4)
lib/canonical/launchpad/webapp/publication.py (+1/-1)
lib/canonical/launchpad/webapp/tests/test_errorlog.py (+80/-32)
lib/canonical/launchpad/webapp/tests/test_publication.py (+3/-0)
lib/canonical/launchpad/webapp/tests/test_user_requested_oops.py (+10/-0)
lib/lp/bugs/doc/checkwatches.txt (+2/-0)
To merge this branch: bzr merge lp:~matsubara/launchpad/bug-436640
Reviewer Review Type Date Requested Status
Gary Poster (community) Approve
Review via email: mp+13016@code.launchpad.net

Commit message

[r=gary][ui=none][bug=436640] add api to generate informational oopses in errorlog.py and flag DisconnectionErrors and UserRequestOops as informational only.

To post a comment you must log in.
Revision history for this message
Diogo Matsubara (matsubara) wrote :

This branch add a new api: handling() to the ErrorReportingUtility which allows OOPS reports to be flagged as informational only. It also updates DisconnectionErrors and UserRequestOops to use said API.

Tests

$ bin/test -u test_errorlog
$ bin/test -u test_publication
$ bin/test -u test_user_requested_oops

Demo and Q/A

1. Open https://staging.launchpad.net/++oops++
2. Grab the OOPS id in the html source code
3. Wait 10 minutes for the oops to be synced from staging to devpad
4. https://lp-oops.canonical.com/oops/?oopsid=$oops_id_from_step_2
5. Check that the OOPS have the flag Informational: True in the left column

Revision history for this message
Gary Poster (gary) wrote :

Diogo, this is a great branch! I don't have any suggestions. Thank you!

Gary

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/webapp/errorlog.py'
2--- lib/canonical/launchpad/webapp/errorlog.py 2009-08-06 14:25:28 +0000
3+++ lib/canonical/launchpad/webapp/errorlog.py 2009-10-08 01:11:12 +0000
4@@ -132,7 +132,7 @@
5 implements(IErrorReport)
6
7 def __init__(self, id, type, value, time, pageid, tb_text, username,
8- url, duration, req_vars, db_statements):
9+ url, duration, req_vars, db_statements, informational):
10 self.id = id
11 self.type = type
12 self.value = value
13@@ -146,6 +146,7 @@
14 self.db_statements = db_statements
15 self.branch_nick = versioninfo.branch_nick
16 self.revno = versioninfo.revno
17+ self.informational = informational
18
19 def __repr__(self):
20 return '<ErrorReport %s %s: %s>' % (self.id, self.type, self.value)
21@@ -161,6 +162,7 @@
22 fp.write('User: %s\n' % _normalise_whitespace(self.username))
23 fp.write('URL: %s\n' % _normalise_whitespace(self.url))
24 fp.write('Duration: %s\n' % self.duration)
25+ fp.write('Informational: %s\n' % self.informational)
26 fp.write('\n')
27 safe_chars = ';/\\?:@&+$, ()*!'
28 for key, value in self.req_vars:
29@@ -184,6 +186,7 @@
30 username = msg.getheader('user')
31 url = msg.getheader('url')
32 duration = int(float(msg.getheader('duration', '-1')))
33+ informational = msg.getheader('informational')
34
35 # Explicitely use an iterator so we can process the file
36 # sequentially. In most instances the iterator will actually
37@@ -217,7 +220,8 @@
38 tb_text = ''.join(lines)
39
40 return cls(id, exc_type, exc_value, date, pageid, tb_text,
41- username, url, duration, req_vars, statements)
42+ username, url, duration, req_vars, statements,
43+ informational)
44
45
46 class ErrorReportingUtility:
47@@ -396,6 +400,10 @@
48
49 def raising(self, info, request=None, now=None):
50 """See IErrorReportingUtility.raising()"""
51+ self._raising(info, request=request, now=now, informational=False)
52+
53+ def _raising(self, info, request=None, now=None, informational=False):
54+ """Private method used by raising() and handling()."""
55 if now is not None:
56 now = now.astimezone(UTC)
57 else:
58@@ -483,7 +491,8 @@
59
60 entry = ErrorReport(oopsid, strtype, strv, now, pageid, tb_text,
61 username, strurl, duration,
62- req_vars, statements)
63+ req_vars, statements,
64+ informational)
65 entry.write(open(filename, 'wb'))
66
67 if request:
68@@ -495,6 +504,10 @@
69 finally:
70 info = None
71
72+ def handling(self, info, request=None, now=None):
73+ """Flag ErrorReport as informational only."""
74+ self._raising(info, request=request, now=now, informational=True)
75+
76 def _do_copy_to_zlog(self, now, strtype, url, info, oopsid):
77 distant_past = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=UTC)
78 when = _rate_restrict_pool.get(strtype, distant_past)
79@@ -618,7 +631,7 @@
80 request.oopsid is not None or
81 not request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY, False)):
82 return None
83- globalErrorUtility.raising(
84+ globalErrorUtility.handling(
85 (UserRequestOops, UserRequestOops(), None), request)
86 return request.oopsid
87
88
89=== modified file 'lib/canonical/launchpad/webapp/publication.py'
90--- lib/canonical/launchpad/webapp/publication.py 2009-10-01 15:36:20 +0000
91+++ lib/canonical/launchpad/webapp/publication.py 2009-10-08 01:11:12 +0000
92@@ -484,7 +484,7 @@
93 # Log a soft OOPS for DisconnectionErrors as per Bug #373837.
94 # We need to do this before we re-raise the exception as a Retry.
95 if isinstance(exc_info[1], DisconnectionError):
96- getUtility(IErrorReportingUtility).raising(exc_info, request)
97+ getUtility(IErrorReportingUtility).handling(exc_info, request)
98
99 def should_retry(exc_info):
100 if not retry_allowed:
101
102=== modified file 'lib/canonical/launchpad/webapp/tests/test_errorlog.py'
103--- lib/canonical/launchpad/webapp/tests/test_errorlog.py 2009-07-29 14:28:18 +0000
104+++ lib/canonical/launchpad/webapp/tests/test_errorlog.py 2009-10-08 01:11:12 +0000
105@@ -58,7 +58,8 @@
106 [('name1', 'value1'), ('name2', 'value2'),
107 ('name1', 'value3')],
108 [(1, 5, 'store_a', 'SELECT 1'),
109- (5, 10, 'store_b', 'SELECT 2')])
110+ (5, 10, 'store_b', 'SELECT 2')],
111+ False)
112 self.assertEqual(entry.id, 'id')
113 self.assertEqual(entry.type, 'exc-type')
114 self.assertEqual(entry.value, 'exc-value')
115@@ -69,6 +70,7 @@
116 self.assertEqual(entry.username, 'username')
117 self.assertEqual(entry.url, 'url')
118 self.assertEqual(entry.duration, 42)
119+ self.assertEqual(entry.informational, False)
120 self.assertEqual(len(entry.req_vars), 3)
121 self.assertEqual(entry.req_vars[0], ('name1', 'value1'))
122 self.assertEqual(entry.req_vars[1], ('name2', 'value2'))
123@@ -93,7 +95,7 @@
124 ('HTTP_REFERER', 'http://localhost:9000/'),
125 ('name=foo', 'hello\nworld')],
126 [(1, 5, 'store_a', 'SELECT 1'),
127- (5, 10,'store_b', 'SELECT\n2')])
128+ (5, 10,'store_b', 'SELECT\n2')], False)
129 fp = StringIO.StringIO()
130 entry.write(fp)
131 self.assertEqual(fp.getvalue(), dedent("""\
132@@ -107,6 +109,7 @@
133 User: Sample User
134 URL: http://localhost:9000/foo
135 Duration: 42
136+ Informational: False
137
138 HTTP_USER_AGENT=Mozilla/5.0
139 HTTP_REFERER=http://localhost:9000/
140@@ -377,19 +380,20 @@
141 self.assertEqual(lines[7], 'User: None\n')
142 self.assertEqual(lines[8], 'URL: None\n')
143 self.assertEqual(lines[9], 'Duration: -1\n')
144- self.assertEqual(lines[10], '\n')
145+ self.assertEqual(lines[10], 'Informational: False\n')
146+ self.assertEqual(lines[11], '\n')
147
148 # no request vars
149- self.assertEqual(lines[11], '\n')
150+ self.assertEqual(lines[12], '\n')
151
152 # no database statements
153- self.assertEqual(lines[12], '\n')
154+ self.assertEqual(lines[13], '\n')
155
156 # traceback
157- self.assertEqual(lines[13], 'Traceback (most recent call last):\n')
158+ self.assertEqual(lines[14], 'Traceback (most recent call last):\n')
159 # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
160 # raise ArbitraryException(\'xyz\')
161- self.assertEqual(lines[16], 'ArbitraryException: xyz\n')
162+ self.assertEqual(lines[17], 'ArbitraryException: xyz\n')
163
164 def test_raising_with_request(self):
165 """Test ErrorReportingUtility.raising() with a request"""
166@@ -432,6 +436,7 @@
167 lines.pop(0), 'User: Login, 42, title, description |\\u25a0|\n')
168 self.assertEqual(lines.pop(0), 'URL: http://localhost:9000/foo\n')
169 self.assertEqual(lines.pop(0), 'Duration: -1\n')
170+ self.assertEqual(lines.pop(0), 'Informational: False\n')
171 self.assertEqual(lines.pop(0), '\n')
172
173 # request vars
174@@ -479,7 +484,7 @@
175 errorfile = os.path.join(utility.errordir(now), '01800.T1')
176 self.assertTrue(os.path.exists(errorfile))
177 lines = open(errorfile, 'r').readlines()
178- self.assertEqual(lines[15], 'xmlrpc args=(1, 2)\n')
179+ self.assertEqual(lines[16], 'xmlrpc args=(1, 2)\n')
180
181 def test_raising_with_webservice_request(self):
182 # Test ErrorReportingUtility.raising() with a WebServiceRequest
183@@ -546,22 +551,23 @@
184 self.assertEqual(lines[7], 'User: None\n')
185 self.assertEqual(lines[8], 'URL: https://launchpad.net/example\n')
186 self.assertEqual(lines[9], 'Duration: -1\n')
187- self.assertEqual(lines[10], '\n')
188+ self.assertEqual(lines[10], 'Informational: False\n')
189+ self.assertEqual(lines[11], '\n')
190
191 # request vars
192- self.assertEqual(lines[11], 'name1=value1\n')
193- self.assertEqual(lines[12], 'name1=value3\n')
194- self.assertEqual(lines[13], 'name2=value2\n')
195- self.assertEqual(lines[14], '\n')
196-
197- # no database statements
198+ self.assertEqual(lines[12], 'name1=value1\n')
199+ self.assertEqual(lines[13], 'name1=value3\n')
200+ self.assertEqual(lines[14], 'name2=value2\n')
201 self.assertEqual(lines[15], '\n')
202
203+ # no database statements
204+ self.assertEqual(lines[16], '\n')
205+
206 # traceback
207- self.assertEqual(lines[16], 'Traceback (most recent call last):\n')
208+ self.assertEqual(lines[17], 'Traceback (most recent call last):\n')
209 # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
210 # raise ArbitraryException(\'xyz\')
211- self.assertEqual(lines[19], 'ArbitraryException: xyz\n')
212+ self.assertEqual(lines[20], 'ArbitraryException: xyz\n')
213
214 # verify that the oopsid was set on the request
215 self.assertEqual(request.oopsid, 'OOPS-91T1')
216@@ -598,20 +604,21 @@
217 self.assertEqual(lines[7], 'User: None\n')
218 self.assertEqual(lines[8], 'URL: None\n')
219 self.assertEqual(lines[9], 'Duration: -1\n')
220- self.assertEqual(lines[10], '\n')
221+ self.assertEqual(lines[10], 'Informational: False\n')
222+ self.assertEqual(lines[11], '\n')
223
224 # no request vars
225- self.assertEqual(lines[11], '\n')
226+ self.assertEqual(lines[12], '\n')
227
228 # no database statements
229- self.assertEqual(lines[12], '\n')
230+ self.assertEqual(lines[13], '\n')
231
232 # traceback
233- self.assertEqual(lines[13], 'Traceback (most recent call last):\n')
234+ self.assertEqual(lines[14], 'Traceback (most recent call last):\n')
235 # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
236 # raise UnprintableException()
237 self.assertEqual(
238- lines[16], 'UnprintableException: <unprintable instance object>\n'
239+ lines[17], 'UnprintableException: <unprintable instance object>\n'
240 )
241
242 def test_raising_unauthorized_without_request(self):
243@@ -715,16 +722,57 @@
244 self.assertEqual(lines[7], 'User: None\n')
245 self.assertEqual(lines[8], 'URL: None\n')
246 self.assertEqual(lines[9], 'Duration: -1\n')
247- self.assertEqual(lines[10], '\n')
248-
249- # no request vars
250- self.assertEqual(lines[11], '\n')
251-
252- # no database statements
253- self.assertEqual(lines[12], '\n')
254-
255- # traceback
256- self.assertEqual(''.join(lines[13:17]), exc_tb)
257+ self.assertEqual(lines[10], 'Informational: False\n')
258+ self.assertEqual(lines[11], '\n')
259+
260+ # no request vars
261+ self.assertEqual(lines[12], '\n')
262+
263+ # no database statements
264+ self.assertEqual(lines[13], '\n')
265+
266+ # traceback
267+ self.assertEqual(''.join(lines[14:18]), exc_tb)
268+
269+ def test_handling(self):
270+ """Test ErrorReportingUtility.handling()."""
271+ utility = ErrorReportingUtility()
272+ now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC)
273+
274+ try:
275+ raise ArbitraryException('xyz')
276+ except ArbitraryException:
277+ utility.handling(sys.exc_info(), now=now)
278+
279+ errorfile = os.path.join(utility.errordir(now), '01800.T1')
280+ self.assertTrue(os.path.exists(errorfile))
281+ lines = open(errorfile, 'r').readlines()
282+
283+ # the header
284+ self.assertEqual(lines[0], 'Oops-Id: OOPS-91T1\n')
285+ self.assertEqual(lines[1], 'Exception-Type: ArbitraryException\n')
286+ self.assertEqual(lines[2], 'Exception-Value: xyz\n')
287+ self.assertEqual(lines[3], 'Date: 2006-04-01T00:30:00+00:00\n')
288+ self.assertEqual(lines[4], 'Page-Id: \n')
289+ self.assertEqual(lines[5], 'Branch: %s\n' % versioninfo.branch_nick)
290+ self.assertEqual(lines[6], 'Revision: %s\n'% versioninfo.revno)
291+ self.assertEqual(lines[7], 'User: None\n')
292+ self.assertEqual(lines[8], 'URL: None\n')
293+ self.assertEqual(lines[9], 'Duration: -1\n')
294+ self.assertEqual(lines[10], 'Informational: True\n')
295+ self.assertEqual(lines[11], '\n')
296+
297+ # no request vars
298+ self.assertEqual(lines[12], '\n')
299+
300+ # no database statements
301+ self.assertEqual(lines[13], '\n')
302+
303+ # traceback
304+ self.assertEqual(lines[14], 'Traceback (most recent call last):\n')
305+ # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
306+ # raise ArbitraryException(\'xyz\')
307+ self.assertEqual(lines[17], 'ArbitraryException: xyz\n')
308
309
310 class TestSensitiveRequestVariables(unittest.TestCase):
311
312=== modified file 'lib/canonical/launchpad/webapp/tests/test_publication.py'
313--- lib/canonical/launchpad/webapp/tests/test_publication.py 2009-09-17 14:29:22 +0000
314+++ lib/canonical/launchpad/webapp/tests/test_publication.py 2009-10-08 01:11:12 +0000
315@@ -115,6 +115,9 @@
316 # Ensure the OOPS mentions the correct exception
317 self.assertNotEqual(repr(next_oops).find("DisconnectionError"), -1)
318
319+ # Ensure the OOPS is correctly marked as informational only.
320+ self.assertEqual(next_oops.informational, 'True')
321+
322 # Ensure that it is different to the last logged OOPS.
323 self.assertNotEqual(repr(last_oops), repr(next_oops))
324
325
326=== modified file 'lib/canonical/launchpad/webapp/tests/test_user_requested_oops.py'
327--- lib/canonical/launchpad/webapp/tests/test_user_requested_oops.py 2009-07-22 23:31:29 +0000
328+++ lib/canonical/launchpad/webapp/tests/test_user_requested_oops.py 2009-10-08 01:11:12 +0000
329@@ -6,6 +6,9 @@
330
331 import unittest
332
333+from zope.component import getUtility
334+from zope.error.interfaces import IErrorReportingUtility
335+
336 from lazr.restful.utils import get_current_browser_request
337
338 from canonical.launchpad.webapp.errorlog import (
339@@ -68,6 +71,13 @@
340 self.assertIs(context, result)
341 self.assertTrue(request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY))
342
343+ def test_user_requested_oops_marked_informational(self):
344+ # User requested oopses are flagged as informational only.
345+ error_reporting_utility = getUtility(IErrorReportingUtility)
346+ last_oops = error_reporting_utility.getLastOopsReport()
347+ self.assertEqual(last_oops.type, 'UserRequestOops')
348+ self.assertEqual(last_oops.informational, 'True')
349+
350
351 def test_suite():
352 return unittest.TestLoader().loadTestsFromName(__name__)
353
354=== modified file 'lib/lp/bugs/doc/checkwatches.txt'
355--- lib/lp/bugs/doc/checkwatches.txt 2009-10-05 09:18:38 +0000
356+++ lib/lp/bugs/doc/checkwatches.txt 2009-10-08 01:11:12 +0000
357@@ -86,6 +86,7 @@
358 User: None
359 URL: None
360 Duration: -1
361+ Informational: False
362 <BLANKLINE>
363 error-explanation=ExternalBugtracker for ... is not known.
364
365@@ -157,6 +158,7 @@
366 User: None
367 URL: http://bugs.example.com
368 Duration: -1
369+ Informational: False
370 <BLANKLINE>
371 baseurl=http://bugs.example.com
372 bugtracker=example-bugs

Subscribers

People subscribed via source and target branches

to status/vote changes: