Merge lp:~elachuni/ubuntu-recommender/oops-ids into lp:ubuntu-recommender
- oops-ids
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 80 |
Proposed branch: | lp:~elachuni/ubuntu-recommender/oops-ids |
Merge into: | lp:ubuntu-recommender |
Diff against target: |
555 lines (+222/-126) 12 files modified
.bzrignore (+1/-0) django_project/config/main.cfg (+2/-2) fabtasks/bootstrap.py (+7/-5) requirements.txt (+1/-1) src/recommender/management/commands/runserver.py (+20/-0) src/recommender/schema.py (+2/-1) src/recommender/templates/500.html (+9/-112) src/recommender/tests/__init__.py (+1/-0) src/recommender/tests/test_commands.py (+3/-4) src/recommender/tests/test_wsgi.py (+122/-0) src/recommender/views.py (+3/-1) src/recommender/wsgi.py (+51/-0) |
To merge this branch: | bzr merge lp:~elachuni/ubuntu-recommender/oops-ids |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Anthony Lenton (community) | Approve | ||
Review via email: mp+150096@code.launchpad.net |
Commit message
Added a custom wsgi handler to get oops ids on all error responses.
Description of the change
This branch adds a custom wsgi handler to get oops ids on all error pages, and in API errors too.
It also customizes Django's runserver command to use our custom wsgi stack, so that now oops reports are also generated in dev as they are in prod. Dev only has a datedir publisher configured by default.
Also, it updates to oops-dictconfig 0.0.4 so that we can set inherit_id=True on our publishers.
While I was there, I removed all static external resources from the error pages, that aren't set up on staging/production and make the error pages look ugly. If we can provide better error pages eventually, great.
Finally, I also made 'fab bootstrap' not start from scratch every time. If you want the current behaviour you will now need to do 'fab clean bootstrap'.
James Westby (james-w) wrote : | # |
Anthony Lenton (elachuni) : | # |
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~elachuni/ubuntu-recommender/oops-ids into lp:ubuntu-recommender failed. Below is the output from the failed tests.
[localhost] local: /usr/bin/python /usr/bin/virtualenv --no-site-packages virtualenv
The --no-site-packages flag is deprecated; it is now the default behavior.
New python executable in virtualenv/
Installing distribute.
Installing pip....
[localhost] local: virtualenv/bin/pip install -r requirements.txt
Downloading/
Running setup.py egg_info for package bzr
No Cython, trying Pyrex...
The python package 'Pyrex' is not available. If the .c files are available,
they will be built, but modifying the .pyx files will not rebuild them.
Downloading/
Downloading configglue-
Running setup.py egg_info for package configglue
Downloading/
Running setup.py egg_info for package coverage
warning: no previously-included files matching '*.pyc' found anywhere in distribution
Downloading/
Running setup.py egg_info for package django
Downloading/
Downloading django-
Running setup.py egg_info for package django-configglue
warning: no previously-included files matching '*.pyc' found anywhere in distribution
Obtaining django-pgtools from bzr+http://
Checking out http://
Running setup.py egg_info for package django-pgtools
Obtaining django-openid-auth from bzr+http://
Checking out http://
Running setup.py egg_info for package django-openid-auth
Downloading/
Downloading django-
Running setup.py egg_info for package django-piston
Downloading/
Running setup.py egg_info for package fabric
warning: no previously-included files matching '*' found under directory 'docs/_build'
warning: no previously-included files matching '*.pyc' found under directory 'tests'
warning: no previously-included files matching '*.pyo' found under directory 'tests'
Downloading/
Running setup.py egg_info for package mock
warning: no files found matching '*.png' under directory 'docs'
warning: no files found ma...
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2013-02-20 20:34:25 +0000 |
3 | +++ .bzrignore 2013-02-25 12:37:19 +0000 |
4 | @@ -11,3 +11,4 @@ |
5 | rec-webapp.log |
6 | rec-auth.log |
7 | data |
8 | +oopses/ |
9 | |
10 | === modified file 'django_project/config/main.cfg' |
11 | --- django_project/config/main.cfg 2012-09-28 21:24:33 +0000 |
12 | +++ django_project/config/main.cfg 2013-02-25 12:37:19 +0000 |
13 | @@ -4,7 +4,7 @@ |
14 | database_engine = sqlite3 |
15 | database_host = |
16 | database_name = test.db |
17 | -debug = true |
18 | +debug = false |
19 | email_host = localhost |
20 | email_port = 1025 |
21 | email_backend = django.core.mail.backends.locmem.EmailBackend |
22 | @@ -125,7 +125,7 @@ |
23 | rnr_api = https://reviews.ubuntu.com/reviews/api/1.0 |
24 | |
25 | [logging] |
26 | -webapp_logging_config = staging_logging.conf |
27 | +webapp_logging_config = django_project/config/staging_logging.conf |
28 | |
29 | [preflight] |
30 | preflight_groups = hackers |
31 | |
32 | === modified file 'fabtasks/bootstrap.py' |
33 | --- fabtasks/bootstrap.py 2013-02-20 20:34:25 +0000 |
34 | +++ fabtasks/bootstrap.py 2013-02-25 12:37:19 +0000 |
35 | @@ -1,13 +1,12 @@ |
36 | import os |
37 | import sys |
38 | |
39 | -from fabric.api import local |
40 | +from fabric.api import local, lcd |
41 | |
42 | from .database import create_database |
43 | |
44 | |
45 | def virtualenv_create(): |
46 | - local("rm -rf virtualenv") |
47 | local("%s /usr/bin/virtualenv --no-site-packages virtualenv" |
48 | % sys.executable, capture=False) |
49 | |
50 | @@ -19,9 +18,12 @@ |
51 | revision = "--revision={0}".format(revision) |
52 | branch_path = os.path.join("branches", name) |
53 | if os.path.exists(branch_path): |
54 | - local("rm -r {0}".format(branch_path)) |
55 | - local("bzr branch {0} {1} {2}".format( |
56 | - revision, repo, branch_path), capture=False) |
57 | + with lcd(branch_path): |
58 | + local("bzr pull {0} {1}".format( |
59 | + revision, repo), capture=False) |
60 | + else: |
61 | + local("bzr branch {0} {1} {2}".format( |
62 | + revision, repo, branch_path), capture=False) |
63 | |
64 | |
65 | def _symlink(source, link_name): |
66 | |
67 | === modified file 'requirements.txt' |
68 | --- requirements.txt 2013-02-20 20:34:25 +0000 |
69 | +++ requirements.txt 2013-02-25 12:37:19 +0000 |
70 | @@ -15,6 +15,6 @@ |
71 | django-factory |
72 | psycopg2==2.4.1 |
73 | oops-wsgi==0.0.10 |
74 | -oops-dictconfig==0.0.1 |
75 | +oops-dictconfig==0.0.4 |
76 | # XXX 2012-03-07 bug=948857 Currently this pulls in lazr.restfulclient and co. |
77 | oops_datedir_repo==0.0.17 |
78 | |
79 | === added file 'src/recommender/management/commands/runserver.py' |
80 | --- src/recommender/management/commands/runserver.py 1970-01-01 00:00:00 +0000 |
81 | +++ src/recommender/management/commands/runserver.py 2013-02-25 12:37:19 +0000 |
82 | @@ -0,0 +1,20 @@ |
83 | +# Slightly customized version of Django's runserver command |
84 | +# that runs ubuntu-recommender's wsgi stack. |
85 | + |
86 | +from optparse import make_option |
87 | +from django.core.management.commands.runserver import BaseRunserverCommand |
88 | +from django.core.servers.basehttp import AdminMediaHandler |
89 | + |
90 | + |
91 | +class Command(BaseRunserverCommand): |
92 | + option_list = BaseRunserverCommand.option_list + ( |
93 | + make_option( |
94 | + '--adminmedia', dest='admin_media_path', default='', |
95 | + help='Specifies the directory from which to serve admin media.'), |
96 | + ) |
97 | + |
98 | + def get_handler(self, *args, **options): |
99 | + """Serves admin media like old-school (deprecation pending).""" |
100 | + from recommender.wsgi import make_app |
101 | + handler = make_app() |
102 | + return AdminMediaHandler(handler, options.get('admin_media_path', '')) |
103 | |
104 | === modified file 'src/recommender/schema.py' |
105 | --- src/recommender/schema.py 2013-02-20 20:34:25 +0000 |
106 | +++ src/recommender/schema.py 2013-02-25 12:37:19 +0000 |
107 | @@ -114,7 +114,8 @@ |
108 | { |
109 | 'type': 'datedir', |
110 | 'error_dir': 'oopses', |
111 | - 'instance_id': 'dev', |
112 | + 'inherit_id': True, |
113 | + 'new_only': False, |
114 | } |
115 | ], |
116 | } |
117 | |
118 | === modified file 'src/recommender/templates/500.html' |
119 | --- src/recommender/templates/500.html 2012-08-17 22:20:06 +0000 |
120 | +++ src/recommender/templates/500.html 2013-02-25 12:37:19 +0000 |
121 | @@ -34,7 +34,7 @@ |
122 | body { |
123 | font-family: 'Ubuntu Beta', UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif; |
124 | color: #333; |
125 | - background: white url(../images/body_bg.png); |
126 | + background: white; |
127 | font-size: 12px; |
128 | line-height: 16px; |
129 | margin: 0px; |
130 | @@ -57,62 +57,13 @@ |
131 | -webkit-box-shadow: #bbb 0px 0px 5px; |
132 | } |
133 | #header { |
134 | - background: #dd4814 url(../images/header_bg.png) top left repeat-x; |
135 | + background: #dd4814 top left repeat-x; |
136 | height: 64px; |
137 | margin: 0px; |
138 | padding: 0px; |
139 | position: relative; |
140 | } |
141 | |
142 | -#menu-search { |
143 | - height: 40px; |
144 | - margin: 0 16px; |
145 | -} |
146 | -#search-box { |
147 | - margin-left: 720px; |
148 | - -moz-border-radius: 0px 0px 4px 4px; |
149 | - -webkit-border-bottom-left-radius: 4px; |
150 | - -webkit-border-bottom-right-radius: 4px; |
151 | - -moz-box-shadow: #bbb 0px 0px 2px; |
152 | - -webkit-box-shadow: #bbb 0px 0px 2px; |
153 | - background-color: #f2f2f2; |
154 | - width: 224px; |
155 | - height: 40px; |
156 | -} |
157 | -#search-box-input { |
158 | - float: left; |
159 | - width: 172px; |
160 | - height: 20px; |
161 | - border-color: #d1d1d1; |
162 | - border-style: solid none none solid; |
163 | - border-width: 1px medium medium 1px; |
164 | - margin: 8px 0 0 8px; |
165 | - padding: 1px; |
166 | - font-size: 12px; |
167 | -} |
168 | -#search-box-button { |
169 | - float: left; |
170 | - background: url(../images/search_submit_bg_2.png) transparent; |
171 | - width: 24px; |
172 | - height: 24px; |
173 | - margin: 8px 0 0 8px; |
174 | - padding: 0; |
175 | - border: none; |
176 | - cursor: pointer; |
177 | -} |
178 | -#search-box-button:hover { |
179 | - background-position: 0 -23px; |
180 | -} |
181 | -#search-box-button:active { |
182 | - background-position: 0 -46px; |
183 | -} |
184 | - |
185 | -#search-box button span { |
186 | - position: absolute; |
187 | - overflow: hidden; |
188 | - text-indent: -9999px; |
189 | -} |
190 | - |
191 | #title { |
192 | padding: 28px 24px; |
193 | } |
194 | @@ -134,15 +85,12 @@ |
195 | line-height: 14px; |
196 | } |
197 | #copyright a { |
198 | - background-image: url(../images/footer_logo.png); |
199 | width: 118px; |
200 | height: 27px; |
201 | display: block; |
202 | float: right; |
203 | } |
204 | #ubuntu-header { |
205 | - background-image: url(../images/header_logo.png); |
206 | - background-repeat: no-repeat; |
207 | height: 32px; |
208 | left: 841px; |
209 | margin: 0; |
210 | @@ -159,13 +107,12 @@ |
211 | width: 100%; |
212 | } |
213 | |
214 | -#main-menu, { |
215 | +#main-menu { |
216 | list-style: none; |
217 | margin: 0; |
218 | margin-left: 16px; |
219 | padding: 0; |
220 | height: 100%; |
221 | - background: url(../images/topnav_divider.png) no-repeat scroll left top transparent !important; |
222 | } |
223 | #main-menu li { |
224 | float: left; |
225 | @@ -174,7 +121,6 @@ |
226 | height: 64px; |
227 | font-size: 14px; |
228 | line-height: 16px; |
229 | - background: url(../images/topnav_divider.png) no-repeat scroll right top transparent !important; |
230 | } |
231 | #main-menu a { |
232 | display: block; |
233 | @@ -185,40 +131,6 @@ |
234 | color: #fff; |
235 | text-shadow: #000 0px 1px; |
236 | } |
237 | -#main-menu a:hover, #main-menu li.active a, |
238 | -#main-menu li ul { |
239 | - position: absolute; |
240 | - top: 64px; |
241 | - left: 16px; |
242 | - height: 40px; |
243 | - width: 716px; |
244 | - background-color: #F2F2F2; |
245 | - -moz-border-radius: 0px 0px 4px 4px; |
246 | - -webkit-border-radius: 0px 0px 4px 4px; |
247 | - border-radius: 0px 0px 4px 4px; |
248 | - -moz-box-shadow: #bbb 0px 0px 2px; |
249 | - -webkit-box-shadow: #bbb 0px 0px 2px; |
250 | - box-shadow: #bbb 0px 0px 2px; |
251 | -} |
252 | -#main-menu li li { |
253 | - height: 40px; |
254 | - padding: 0; |
255 | - background-color: #F2F2F2; |
256 | - background-image: none; |
257 | - list-style-image: none; |
258 | -} |
259 | -#main-menu li.active li a { |
260 | - padding: 13px 8px 0px 10px; |
261 | - height: 27px; |
262 | - background-color: #F2F2F2; |
263 | - color: #333; |
264 | - text-shadow: none; |
265 | - font-size: 12px; |
266 | - background: transparent none !important; |
267 | -} |
268 | -#main-menu li.active li a:hover { |
269 | - color: #772953; |
270 | -} |
271 | h1, h2, h3, h4, h5 { |
272 | padding: 0; |
273 | margin: 0; |
274 | @@ -237,12 +149,11 @@ |
275 | color: #dd4814; |
276 | text-decoration: none; |
277 | } |
278 | -a:hover { |
279 | +#main-menu a:hover { |
280 | text-decoration: underline; |
281 | } |
282 | ul { |
283 | margin-bottom: 16px; |
284 | - list-style-image: url(../images/bullet.png); |
285 | } |
286 | ul li { |
287 | margin-bottom: 8px; |
288 | @@ -285,29 +196,15 @@ |
289 | <li><a href="http://shop.canonical.com/">Shop</a></li> |
290 | </ul> |
291 | </div> |
292 | - <div id="menu-search"> |
293 | - {% block search %} |
294 | - <div id="search-box"> |
295 | - <form id="sitesearch" action="http://www.ubuntu.com/search/node" method="post"> |
296 | - <input type="text" maxlength="128" name="search_theme_form" id="search-box-input" |
297 | - value="Type to search" title="Enter the terms you wish to search for." |
298 | - onblur="if(this.value==''){this.value='Type to search';}" |
299 | - onfocus="if(this.value=='Type to search'){this.value=''}" |
300 | - /> |
301 | - <button type="submit" name="op" id="search-box-button"><span>go</span></button> |
302 | - <input type="hidden" name="form_build_id" id="form-967ff0ccc1a2f6f6d92f3e8c0822866d" value="form-967ff0ccc1a2f6f6d92f3e8c0822866d" /> |
303 | - <input type="hidden" name="form_token" id="a-unique-id" value="656a58b3b2c4f37a2af1d6b188a4a595" /> |
304 | - <input type="hidden" name="form_id" id="edit-search-theme-form" value="search_theme_form" /> |
305 | - </form> |
306 | - </div> |
307 | - {% endblock %} |
308 | - </div> |
309 | <div id="title"> |
310 | - <h1>{% block header %}Internal Server Error{% endblock %}</h1> |
311 | + <h1>{% block header %}Oops!{% endblock %}</h1> |
312 | </div> |
313 | {% block content_container %} |
314 | <div id="content" class="clearfix content-area"> |
315 | -{% block content %}There's been an error. It's been reported to the site administrators and should be fixed shortly. Thanks for your patience.{% endblock %} |
316 | +{% block content %} |
317 | + <p>There's been an error. It's been reported to the site administrators and should be fixed shortly. Thanks for your patience.</p> |
318 | + <p>{{ oops_id }}</p> |
319 | +{% endblock %} |
320 | </div> |
321 | {% endblock %} |
322 | </div> |
323 | |
324 | === modified file 'src/recommender/tests/__init__.py' |
325 | --- src/recommender/tests/__init__.py 2013-02-20 17:19:26 +0000 |
326 | +++ src/recommender/tests/__init__.py 2013-02-25 12:37:19 +0000 |
327 | @@ -12,3 +12,4 @@ |
328 | from .test_slopeone import * |
329 | from .test_utilities import * |
330 | from .test_views_oops import * |
331 | +from .test_wsgi import * |
332 | |
333 | === modified file 'src/recommender/tests/test_commands.py' |
334 | --- src/recommender/tests/test_commands.py 2012-09-28 21:24:33 +0000 |
335 | +++ src/recommender/tests/test_commands.py 2013-02-25 12:37:19 +0000 |
336 | @@ -168,9 +168,8 @@ |
337 | |
338 | ImportRnRData().handle(verbosity='1', bootstrap=True) |
339 | |
340 | - self.assertTrue( |
341 | - "Processing reviews for super-package" in |
342 | - sys.stdout.getvalue()) |
343 | + expected = "Processing reviews for super-package" |
344 | + self.assertIn(expected, sys.stdout.getvalue()) |
345 | |
346 | sys.stdout = _stdout |
347 | |
348 | @@ -263,7 +262,7 @@ |
349 | |
350 | last_output = self.mock_stdout.write.call_args_list[-1][0][0] |
351 | expected = 'Error fetching data: No JSON object could be decoded\n' |
352 | - self.assertEqual(expected, last_output) |
353 | + self.assertIn(expected, last_output) |
354 | |
355 | @patch('httplib2.Http.request') |
356 | def test_department_is_updated(self, mock_request): |
357 | |
358 | === added file 'src/recommender/tests/test_wsgi.py' |
359 | --- src/recommender/tests/test_wsgi.py 1970-01-01 00:00:00 +0000 |
360 | +++ src/recommender/tests/test_wsgi.py 2013-02-25 12:37:19 +0000 |
361 | @@ -0,0 +1,122 @@ |
362 | +import bson |
363 | +import json |
364 | +import os |
365 | +import re |
366 | + |
367 | +from tempfile import mkdtemp |
368 | +from shutil import rmtree |
369 | + |
370 | +from django.core.urlresolvers import reverse |
371 | +from django.test import TestCase |
372 | +from mock import Mock, patch |
373 | + |
374 | +from recommender.tests.helpers import patch_settings |
375 | +from recommender.wsgi import make_app |
376 | + |
377 | +oopsid_re = re.compile('OOPS-[0-9a-f]{32}') |
378 | + |
379 | + |
380 | +class WSGIDispatchTestCase(TestCase): |
381 | + |
382 | + def setUp(self): |
383 | + """Create a temporary directory for oopses.""" |
384 | + self.oops_dir = mkdtemp() |
385 | + self.addCleanup(rmtree, self.oops_dir) |
386 | + |
387 | + self.environ = { |
388 | + 'PATH_INFO': '/', |
389 | + 'QUERY_STRING': '', |
390 | + 'REMOTE_ADDR': '127.0.0.1', |
391 | + 'REQUEST_METHOD': 'GET', |
392 | + 'SCRIPT_NAME': '', |
393 | + 'SERVER_NAME': 'testserver', |
394 | + 'SERVER_PORT': '80', |
395 | + 'SERVER_PROTOCOL': 'HTTP/1.1', |
396 | + 'wsgi.version': (1, 0), |
397 | + 'wsgi.url_scheme': 'http', |
398 | + 'wsgi.multiprocess': True, |
399 | + 'wsgi.multithread': False, |
400 | + 'wsgi.run_once': False, |
401 | + 'wsgi.input': '', |
402 | + } |
403 | + |
404 | + def request_wsgi_response(self): |
405 | + start_response = Mock() |
406 | + oopses = { |
407 | + 'publishers': [{ |
408 | + 'type': 'datedir', |
409 | + 'error_dir': self.oops_dir, |
410 | + 'instance_id': 'dev', |
411 | + }], |
412 | + } |
413 | + with patch_settings(OOPSES=oopses): |
414 | + app = make_app() |
415 | + response = app(self.environ, start_response).next() |
416 | + return response |
417 | + |
418 | + def request_api_exception(self, data={}): |
419 | + """Helper for creating exceptions when calling api.""" |
420 | + self.environ['PATH_INFO'] = '/api/1.0/recommend_app/firefox/' |
421 | + self.environ['ACCEPT'] = 'application/json' |
422 | + packages_filter_fn = ( |
423 | + 'recommender.models.Package.objects.filter') |
424 | + with patch(packages_filter_fn) as mock_packages_filter: |
425 | + mock_packages_filter.side_effect = ZeroDivisionError |
426 | + response = self.request_wsgi_response() |
427 | + return response |
428 | + |
429 | + def request_view_exception(self): |
430 | + """Helper for creating exceptions when calling a view.""" |
431 | + self.environ['PATH_INFO'] = reverse('debug-die') |
432 | + return self.request_wsgi_response() |
433 | + |
434 | + def read_oops_from_disk(self): |
435 | + """Helper to read in the oops written to disk.""" |
436 | + # There is only one date directory in the oops directory. |
437 | + dated_dirs = os.listdir(self.oops_dir) |
438 | + self.assertEqual(1, len(dated_dirs)) |
439 | + |
440 | + # There is only one oops. |
441 | + todays_oops_path = os.path.join(self.oops_dir, dated_dirs[0]) |
442 | + oops_list = os.listdir(todays_oops_path) |
443 | + self.assertEqual(1, len(oops_list)) |
444 | + |
445 | + oops_path = os.path.join(todays_oops_path, oops_list[0]) |
446 | + with open(oops_path) as oops_file: |
447 | + oops_file_contents = oops_file.read() |
448 | + return bson.loads(oops_file_contents) |
449 | + |
450 | + def test_traceback_in_api_response(self): |
451 | + response = self.request_api_exception() |
452 | + |
453 | + data = json.loads(response) |
454 | + self.assertTrue(oopsid_re.match(data['oops_id'])) |
455 | + self.assertTrue('traceback' in data) |
456 | + |
457 | + def test_traceback_in_api_oops_on_disk(self): |
458 | + """An api oops writes the traceback to the file.""" |
459 | + self.request_api_exception() |
460 | + |
461 | + oops_content = self.read_oops_from_disk() |
462 | + self.assertIn('id', oops_content) |
463 | + self.assertIn('tb_text', oops_content) |
464 | + self.assertTrue('raise effect' in oops_content['tb_text']) |
465 | + |
466 | + def test_traceback_in_view_oops_on_disk(self): |
467 | + """A request resulting in an exception writes the traceback to disk.""" |
468 | + self.request_view_exception() |
469 | + |
470 | + # The oops contains the stacktrace as part of a log message. |
471 | + oops_content = self.read_oops_from_disk() |
472 | + self.assertIn('id', oops_content) |
473 | + self.assertIn('tb_text', oops_content) |
474 | + self.assertTrue('ArtificialOopsException' in oops_content['tb_text']) |
475 | + self.assertEqual('ArtificialOopsException', oops_content['type']) |
476 | + |
477 | + def test_traceback_not_in_view_oops_response(self): |
478 | + """An exception doesn't display the traceback.""" |
479 | + response = self.request_view_exception() |
480 | + |
481 | + self.assertTrue(oopsid_re.search(response)) |
482 | + self.assertNotIn(response, 'ArtificialOopsException') |
483 | + self.assertNotIn(response, 'Traceback') |
484 | |
485 | === modified file 'src/recommender/views.py' |
486 | --- src/recommender/views.py 2013-02-20 17:30:06 +0000 |
487 | +++ src/recommender/views.py 2013-02-25 12:37:19 +0000 |
488 | @@ -61,8 +61,10 @@ |
489 | if isinstance(tb_object, traceback.types.TracebackType): |
490 | traceback_formatted = mark_safe("\n".join( |
491 | traceback.format_tb(exc_info[2]))) |
492 | + oops_report = request.environ.get('oops.report', {}) |
493 | + oops_id = oops_report.get('id', 'OOPS ID not available.') |
494 | atts = { |
495 | - 'oops_id': request.META.get('OOPSID', 'OOPS ID not available.'), |
496 | + 'oops_id': oops_id, |
497 | 'traceback': traceback_formatted, |
498 | } |
499 | |
500 | |
501 | === added file 'src/recommender/wsgi.py' |
502 | --- src/recommender/wsgi.py 1970-01-01 00:00:00 +0000 |
503 | +++ src/recommender/wsgi.py 2013-02-25 12:37:19 +0000 |
504 | @@ -0,0 +1,51 @@ |
505 | +import uuid |
506 | +import logging.config |
507 | +import oops_dictconfig |
508 | +import oops_wsgi |
509 | +import platform |
510 | +from django.conf import settings |
511 | +from django.core.handlers import wsgi |
512 | + |
513 | + |
514 | +class EagerOOPSWSGIHandler(wsgi.WSGIHandler): |
515 | + """Pre-generates an oops id on exception.""" |
516 | + def handle_uncaught_exception(self, request, resolver, exc_info): |
517 | + # This will pass the exception information back to oops. It |
518 | + # should not be necessary once |
519 | + # https://code.djangoproject.com/ticket/16674 has been merged. |
520 | + if 'oops.context' in request.environ: |
521 | + request.environ['oops.context']['exc_info'] = exc_info |
522 | + # Generate OOPS id early so that we can render the error page right |
523 | + # away and not depend on passing thread locals around. |
524 | + if 'oops.report' in request.environ: |
525 | + unique_id = uuid.uuid4().hex |
526 | + request.environ['oops.report']['id'] = "OOPS-%s" % unique_id |
527 | + |
528 | + return super(EagerOOPSWSGIHandler, self).handle_uncaught_exception( |
529 | + request, resolver, exc_info) |
530 | + |
531 | + def __call__(self, environ, start_response): |
532 | + def start_response_with_exc_info(status, headers, exc_info=None): |
533 | + """Custom start_response callback for wsgi.""" |
534 | + # This will pass the exception information back to oops_wgi. It |
535 | + # should not be necessary once |
536 | + # https://code.djangoproject.com/ticket/16674 has been merged. |
537 | + if exc_info is None: |
538 | + exc_info = environ['oops.context'].get('exc_info', None) |
539 | + return start_response(status, headers, exc_info) |
540 | + return super(EagerOOPSWSGIHandler, self).__call__( |
541 | + environ, start_response_with_exc_info) |
542 | + |
543 | + |
544 | +def make_app(): |
545 | + """Encapsulate our REC handler in an OOPS app for error reporting.""" |
546 | + logging.config.fileConfig(settings.WEBAPP_LOGGING_CONFIG, |
547 | + disable_existing_loggers=False) |
548 | + |
549 | + non_oops_app = EagerOOPSWSGIHandler() |
550 | + |
551 | + config = oops_dictconfig.config_from_dict(settings.OOPSES) |
552 | + oops_wsgi.install_hooks(config) |
553 | + oops_app = oops_wsgi.make_app(non_oops_app, config, |
554 | + oops_on_status=['500']) |
555 | + return oops_app |
> Also, it updates to oops-dictconfig 0.0.4 so that we can set inherit_id=True on our publishers.
Actually 0.0.4 is needed so you can set 'new_only = False' as well, otherwise you won't get any
oopses saved to disk.
110 'instance_id': 'dev',
I've since learned that setting this is deprecated. It won't matter with the
pre-generation of ids, but please remove it anyway.
339 + self.assertTrue (expected in last_output)
Isn't there assertIn for a better error message?
503 + """Pre-generates an oops id for all requests."""
It only pre-generates an oops id on exception.
Aside from this the oops setup looks good. I haven't reviewed the css/template/ test/fabric changes.
Thanks,
James