Merge lp:~tcole/wsgi-oops/configurable-result-tracking into lp:wsgi-oops

Proposed by Tim Cole
Status: Merged
Approved by: Stuart Colville
Approved revision: 54
Merged at revision: 53
Proposed branch: lp:~tcole/wsgi-oops/configurable-result-tracking
Merge into: lp:wsgi-oops
Diff against target: 247 lines (+106/-39)
4 files modified
canonical/oops/serializer.py (+3/-4)
canonical/oops/tests/test_wsgi.py (+30/-1)
canonical/oops/utils.py (+25/-0)
canonical/oops/wsgi.py (+48/-34)
To merge this branch: bzr merge lp:~tcole/wsgi-oops/configurable-result-tracking
Reviewer Review Type Date Requested Status
Stuart Colville (community) Approve
Martin Albisetti (community) Approve
Review via email: mp+44308@code.launchpad.net

Commit message

Add hooks for request completion tracking.

Description of the change

This lets client code configure the mechanism used for tracking completion of requests for oops accounting. This will be needed in order for wsgi-oops to be compatible with the IBodyProducer wsgi hack.

To post a comment you must log in.
Revision history for this message
Martin Albisetti (beuno) :
review: Approve
Revision history for this message
Stuart Colville (muffinresearch) wrote :

Looks good, tests pass.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'canonical/oops/serializer.py'
2--- canonical/oops/serializer.py 2010-07-06 15:11:35 +0000
3+++ canonical/oops/serializer.py 2010-12-21 03:16:10 +0000
4@@ -55,6 +55,8 @@
5 import weakref
6 import zipfile
7
8+from canonical.oops.utils import resolve_name
9+
10 from pprint import pformat
11 try:
12 from canonical.versioninfo import version_info
13@@ -70,10 +72,7 @@
14
15 def create(serializer_factory_name, *args, **kw):
16 """Creates a serializer instance using the named factory."""
17- factory_path = serializer_factory_name.split(".")
18- module_name = ".".join(factory_path[:-1])
19- module = __import__(module_name)
20- factory = reduce(lambda m, n: getattr(m, n), factory_path[1:], module)
21+ factory = resolve_name(serializer_factory_name)
22 return factory(*args, **kw)
23
24
25
26=== modified file 'canonical/oops/tests/test_wsgi.py'
27--- canonical/oops/tests/test_wsgi.py 2010-07-06 15:45:29 +0000
28+++ canonical/oops/tests/test_wsgi.py 2010-12-21 03:16:10 +0000
29@@ -20,6 +20,8 @@
30 from canonical.oops.tests import testcase
31 from canonical.oops import wsgi
32 from paste.fixture import TestApp
33+from canonical.oops.wsgi import track_iterator_completion
34+
35
36 class TestSimpleWSGIOops(testcase.WSGIOopsTestCase):
37 """ simple test of wsgi oops just test for all ok and error """
38@@ -41,6 +43,14 @@
39 self.assertFalse(self.oops.wanted)
40
41
42+def track_completion(body, on_completion):
43+ """Prove that we've been called by adding to the body."""
44+ yield "OK"
45+ iterator = track_iterator_completion(body, on_completion)
46+ for chunk in iterator:
47+ yield chunk
48+
49+
50 class TestResultIteration(testcase.WSGIOopsTestCase):
51 """Test iteration on results."""
52
53@@ -48,7 +58,7 @@
54 """Test that we start without waiting for body consumption."""
55
56 def dummy_app(environ, start_response):
57- """Produce a response with infinite garbage."""
58+ """Produce a brief response."""
59 start_response("200 OK", {}, exc_info=None)
60 return ["foobar"]
61
62@@ -62,6 +72,25 @@
63 app({}, dummy_start_response)
64 self.assertTrue(started[0])
65
66+ def test_result_tracking(self):
67+ """Test that our own result tracker gets called."""
68+
69+ def dummy_app(environ, start_response):
70+ """Produce a simple response."""
71+ start_response("200 OK", [('Content-Type', 'text/plain')],
72+ exc_info=None)
73+ return ["foobar"]
74+
75+ def dummy_start_response(status, headers, exc_info=None):
76+ """Do nothing."""
77+
78+ name = "canonical.oops.tests.test_wsgi.track_completion"
79+ app = wsgi.OopsWare(dummy_app, self.tmp_dir,
80+ completion_tracker_name=name)
81+ body = app({}, dummy_start_response)
82+ result = list(body)
83+ self.assertEqual(result, ["OK", "foobar"])
84+
85 def _generate_garbage(self):
86 """Generate an infinite sequence of garbage until closed."""
87 while True:
88
89=== added file 'canonical/oops/utils.py'
90--- canonical/oops/utils.py 1970-01-01 00:00:00 +0000
91+++ canonical/oops/utils.py 2010-12-21 03:16:10 +0000
92@@ -0,0 +1,25 @@
93+# Copyright 2008, 2009 Canonical Ltd.
94+#
95+# This file is part of wsgi-oops.
96+#
97+# wsgi-oops is free software: you can redistribute it and/or modify
98+# it under the terms of the GNU Lesser General Public License version 3
99+# as published by the Free Software Foundation.
100+#
101+# wsgi-oops is distributed in the hope that it will be useful,
102+# but WITHOUT ANY WARRANTY; without even the implied warranty of
103+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
104+# GNU Lesser General Public License for more details.
105+#
106+# You should have received a copy of the GNU Lesser General Public License
107+# along with wsgi-oops. If not, see <http://www.gnu.org/licenses/>.
108+
109+"""Miscellaneous utilities."""
110+
111+def resolve_name(name):
112+ """Resolve a name in the python module hierarchy."""
113+ path = name.split(".")
114+ module_name = ".".join(path[:-1])
115+ module = __import__(module_name)
116+ value = reduce(getattr, path[1:], module)
117+ return value
118
119=== modified file 'canonical/oops/wsgi.py'
120--- canonical/oops/wsgi.py 2010-07-06 15:30:55 +0000
121+++ canonical/oops/wsgi.py 2010-12-21 03:16:10 +0000
122@@ -25,7 +25,7 @@
123 """
124
125 __metaclass__ = type
126-__all__ = ["OopsWare"]
127+__all__ = ["OopsWare", "track_iterator_completion"]
128
129 import sys
130 import time
131@@ -36,9 +36,33 @@
132 from canonical.oops import serializer
133 from canonical.oops.oops import (
134 OOPS, OOPSLog, OOPSMetaData, OOPSLoggingHandler )
135+from canonical.oops.utils import resolve_name
136 from canonical.oops.stormtracer import OOPSStorm, OOPSStormTracer
137
138 DEFAULT_SERIALIZER_FACTORY = 'canonical.oops.serializer.OOPSBz2Serializer'
139+DEFAULT_COMPLETION_TRACKER = 'canonical.oops.wsgi.track_iterator_completion'
140+
141+def track_iterator_completion(body, on_completion):
142+ """Wrap the given body iterator in one that tracks completion."""
143+
144+ def iterate():
145+ """Track iterator completion."""
146+ try:
147+ yield None # throwaway value for priming
148+ for chunk in body:
149+ yield chunk
150+ except GeneratorExit:
151+ close = getattr(body, 'close', lambda: None)
152+ close()
153+ on_completion(None, None, None)
154+ except:
155+ on_completion(*sys.exc_info())
156+ else:
157+ on_completion(None, None, None)
158+
159+ iterator = iterate()
160+ iterator.next() # prime generator
161+ return iterator
162
163
164 class OopsWare(object):
165@@ -48,6 +72,7 @@
166 """
167 def __init__(self, app, oops_dir,
168 serializer_factory_name=DEFAULT_SERIALIZER_FACTORY,
169+ completion_tracker_name=DEFAULT_COMPLETION_TRACKER,
170 debug=False, key="appserver"):
171 """
172 Initialise the middleware for oops.
173@@ -66,14 +91,9 @@
174 fh = debug and sys.stderr or None
175 self.serial = serializer.create(serializer_factory_name,
176 key, oops_dir, fh=fh)
177+ self.track_completion = resolve_name(completion_tracker_name)
178
179 def __call__(self, environ, start_response):
180- """Call do_request and pump once to start request processing."""
181- gen = self.do_request(environ, start_response)
182- gen.next() # first value yielded is meant to be discarded
183- return gen
184-
185- def do_request(self, environ, start_response):
186 """
187 Capture exceptions during WSGI application execution and dump
188 oopses as needed.
189@@ -123,37 +143,31 @@
190 setattr(trap_response, 'oops', oops)
191
192 metadata["request-start"] = time.time()
193- try:
194- body = self.app(environ, trap_response)
195- yield "" # thrown away by __call__
196- for chunk in body:
197- yield chunk
198- except GeneratorExit:
199- close = getattr(body, 'close', None)
200- if close:
201- close()
202- except:
203- oops.keep()
204- exc_info = sys.exc_info()
205- try:
206- message = "Unhandled WSGI application exception (OOPS %s)" % \
207- oops.id
208+
209+ def on_completion(ex_type, ex_value, ex_tb):
210+ """Handle request completion."""
211+ if ex_type is not None:
212+ oops.keep()
213+ message = "Unhandled WSGI application exception " \
214+ "(OOPS %s)" % (oops.id,)
215+ exc_info = (ex_type, ex_value, ex_tb)
216 root_logger.error(message, exc_info=exc_info)
217- raise exc_info[0], exc_info[1], exc_info[2]
218- finally:
219- exc_info = None
220- finally:
221+
222 metadata["request-end"] = time.time()
223 if environ.has_key('dump-oops') and not oops.wanted:
224- oops.keep()
225- root_logger.error("Forced dump of OOPS %s" % oops.id)
226- if oops.wanted:
227 try:
228 self.serial.dump(oops)
229 except:
230 exc_info = sys.exc_info()
231- try:
232- message = "Error when dumping OOPS %s" % oops.id
233- root_logger.error(message, exc_info=exc_info)
234- finally:
235- exc_info = None
236+ try:
237+ message = "Error when dumping OOPS %s" % oops.id
238+ root_logger.error(message, exc_info=exc_info)
239+ finally:
240+ exc_info = None
241+
242+ try:
243+ body = self.app(environ, trap_response)
244+ except Exception:
245+ on_completion(*sys.exc_info())
246+ else:
247+ return self.track_completion(body, on_completion)

Subscribers

People subscribed via source and target branches