Merge lp:~elachuni/ubuntu-recommender/oops-ids into lp:ubuntu-recommender

Proposed by Anthony Lenton
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
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'.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

> 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

Revision history for this message
Anthony Lenton (elachuni) :
review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (56.3 KiB)

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/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.
[localhost] local: virtualenv/bin/pip install -r requirements.txt
Downloading/unpacking bzr (from -r requirements.txt (line 1))
  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/unpacking configglue==1.0.1 (from -r requirements.txt (line 2))
  Downloading configglue-1.0.1.tar.gz
  Running setup.py egg_info for package configglue

Downloading/unpacking coverage (from -r requirements.txt (line 3))
  Running setup.py egg_info for package coverage

    warning: no previously-included files matching '*.pyc' found anywhere in distribution
Downloading/unpacking django==1.3 (from -r requirements.txt (line 4))
  Running setup.py egg_info for package django

Downloading/unpacking django-configglue==0.6.1 (from -r requirements.txt (line 5))
  Downloading django-configglue-0.6.1.tar.gz
  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://bazaar.launchpad.net/~canonical-isd-hackers/django-pgtools/trunk (from -r requirements.txt (line 6))
  Checking out http://bazaar.launchpad.net/~canonical-isd-hackers/django-pgtools/trunk to ./virtualenv/src/django-pgtools
  Running setup.py egg_info for package django-pgtools

Obtaining django-openid-auth from bzr+http://bazaar.launchpad.net/~django-openid-auth/django-openid-auth/trunk (from -r requirements.txt (line 7))
  Checking out http://bazaar.launchpad.net/~django-openid-auth/django-openid-auth/trunk to ./virtualenv/src/django-openid-auth
  Running setup.py egg_info for package django-openid-auth

Downloading/unpacking django-piston==0.2.2.1 (from -r requirements.txt (line 8))
  Downloading django-piston-0.2.2.1.tar.gz
  Running setup.py egg_info for package django-piston

Downloading/unpacking fabric (from -r requirements.txt (line 9))
  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/unpacking mock (from -r requirements.txt (line 10))
  Running setup.py egg_info for package mock

    warning: no files found matching '*.png' under directory 'docs'
    warning: no files found ma...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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==&#039;&#039;){this.value=&#039;Type to search&#039;;}"
299- onfocus="if(this.value==&#039;Type to search&#039;){this.value=&#039;&#039;}"
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

Subscribers

People subscribed via source and target branches