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
=== modified file 'canonical/oops/serializer.py'
--- canonical/oops/serializer.py 2010-07-06 15:11:35 +0000
+++ canonical/oops/serializer.py 2010-12-21 03:16:10 +0000
@@ -55,6 +55,8 @@
55import weakref55import weakref
56import zipfile56import zipfile
5757
58from canonical.oops.utils import resolve_name
59
58from pprint import pformat60from pprint import pformat
59try:61try:
60 from canonical.versioninfo import version_info62 from canonical.versioninfo import version_info
@@ -70,10 +72,7 @@
7072
71def create(serializer_factory_name, *args, **kw):73def create(serializer_factory_name, *args, **kw):
72 """Creates a serializer instance using the named factory."""74 """Creates a serializer instance using the named factory."""
73 factory_path = serializer_factory_name.split(".")75 factory = resolve_name(serializer_factory_name)
74 module_name = ".".join(factory_path[:-1])
75 module = __import__(module_name)
76 factory = reduce(lambda m, n: getattr(m, n), factory_path[1:], module)
77 return factory(*args, **kw)76 return factory(*args, **kw)
7877
7978
8079
=== modified file 'canonical/oops/tests/test_wsgi.py'
--- canonical/oops/tests/test_wsgi.py 2010-07-06 15:45:29 +0000
+++ canonical/oops/tests/test_wsgi.py 2010-12-21 03:16:10 +0000
@@ -20,6 +20,8 @@
20from canonical.oops.tests import testcase20from canonical.oops.tests import testcase
21from canonical.oops import wsgi21from canonical.oops import wsgi
22from paste.fixture import TestApp22from paste.fixture import TestApp
23from canonical.oops.wsgi import track_iterator_completion
24
2325
24class TestSimpleWSGIOops(testcase.WSGIOopsTestCase):26class TestSimpleWSGIOops(testcase.WSGIOopsTestCase):
25 """ simple test of wsgi oops just test for all ok and error """27 """ simple test of wsgi oops just test for all ok and error """
@@ -41,6 +43,14 @@
41 self.assertFalse(self.oops.wanted)43 self.assertFalse(self.oops.wanted)
4244
4345
46def track_completion(body, on_completion):
47 """Prove that we've been called by adding to the body."""
48 yield "OK"
49 iterator = track_iterator_completion(body, on_completion)
50 for chunk in iterator:
51 yield chunk
52
53
44class TestResultIteration(testcase.WSGIOopsTestCase):54class TestResultIteration(testcase.WSGIOopsTestCase):
45 """Test iteration on results."""55 """Test iteration on results."""
4656
@@ -48,7 +58,7 @@
48 """Test that we start without waiting for body consumption."""58 """Test that we start without waiting for body consumption."""
4959
50 def dummy_app(environ, start_response):60 def dummy_app(environ, start_response):
51 """Produce a response with infinite garbage."""61 """Produce a brief response."""
52 start_response("200 OK", {}, exc_info=None)62 start_response("200 OK", {}, exc_info=None)
53 return ["foobar"]63 return ["foobar"]
5464
@@ -62,6 +72,25 @@
62 app({}, dummy_start_response)72 app({}, dummy_start_response)
63 self.assertTrue(started[0])73 self.assertTrue(started[0])
6474
75 def test_result_tracking(self):
76 """Test that our own result tracker gets called."""
77
78 def dummy_app(environ, start_response):
79 """Produce a simple response."""
80 start_response("200 OK", [('Content-Type', 'text/plain')],
81 exc_info=None)
82 return ["foobar"]
83
84 def dummy_start_response(status, headers, exc_info=None):
85 """Do nothing."""
86
87 name = "canonical.oops.tests.test_wsgi.track_completion"
88 app = wsgi.OopsWare(dummy_app, self.tmp_dir,
89 completion_tracker_name=name)
90 body = app({}, dummy_start_response)
91 result = list(body)
92 self.assertEqual(result, ["OK", "foobar"])
93
65 def _generate_garbage(self):94 def _generate_garbage(self):
66 """Generate an infinite sequence of garbage until closed."""95 """Generate an infinite sequence of garbage until closed."""
67 while True:96 while True:
6897
=== added file 'canonical/oops/utils.py'
--- canonical/oops/utils.py 1970-01-01 00:00:00 +0000
+++ canonical/oops/utils.py 2010-12-21 03:16:10 +0000
@@ -0,0 +1,25 @@
1# Copyright 2008, 2009 Canonical Ltd.
2#
3# This file is part of wsgi-oops.
4#
5# wsgi-oops is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3
7# as published by the Free Software Foundation.
8#
9# wsgi-oops is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with wsgi-oops. If not, see <http://www.gnu.org/licenses/>.
16
17"""Miscellaneous utilities."""
18
19def resolve_name(name):
20 """Resolve a name in the python module hierarchy."""
21 path = name.split(".")
22 module_name = ".".join(path[:-1])
23 module = __import__(module_name)
24 value = reduce(getattr, path[1:], module)
25 return value
026
=== modified file 'canonical/oops/wsgi.py'
--- canonical/oops/wsgi.py 2010-07-06 15:30:55 +0000
+++ canonical/oops/wsgi.py 2010-12-21 03:16:10 +0000
@@ -25,7 +25,7 @@
25"""25"""
2626
27__metaclass__ = type27__metaclass__ = type
28__all__ = ["OopsWare"]28__all__ = ["OopsWare", "track_iterator_completion"]
2929
30import sys30import sys
31import time31import time
@@ -36,9 +36,33 @@
36from canonical.oops import serializer36from canonical.oops import serializer
37from canonical.oops.oops import (37from canonical.oops.oops import (
38 OOPS, OOPSLog, OOPSMetaData, OOPSLoggingHandler )38 OOPS, OOPSLog, OOPSMetaData, OOPSLoggingHandler )
39from canonical.oops.utils import resolve_name
39from canonical.oops.stormtracer import OOPSStorm, OOPSStormTracer40from canonical.oops.stormtracer import OOPSStorm, OOPSStormTracer
4041
41DEFAULT_SERIALIZER_FACTORY = 'canonical.oops.serializer.OOPSBz2Serializer'42DEFAULT_SERIALIZER_FACTORY = 'canonical.oops.serializer.OOPSBz2Serializer'
43DEFAULT_COMPLETION_TRACKER = 'canonical.oops.wsgi.track_iterator_completion'
44
45def track_iterator_completion(body, on_completion):
46 """Wrap the given body iterator in one that tracks completion."""
47
48 def iterate():
49 """Track iterator completion."""
50 try:
51 yield None # throwaway value for priming
52 for chunk in body:
53 yield chunk
54 except GeneratorExit:
55 close = getattr(body, 'close', lambda: None)
56 close()
57 on_completion(None, None, None)
58 except:
59 on_completion(*sys.exc_info())
60 else:
61 on_completion(None, None, None)
62
63 iterator = iterate()
64 iterator.next() # prime generator
65 return iterator
4266
4367
44class OopsWare(object):68class OopsWare(object):
@@ -48,6 +72,7 @@
48 """72 """
49 def __init__(self, app, oops_dir,73 def __init__(self, app, oops_dir,
50 serializer_factory_name=DEFAULT_SERIALIZER_FACTORY,74 serializer_factory_name=DEFAULT_SERIALIZER_FACTORY,
75 completion_tracker_name=DEFAULT_COMPLETION_TRACKER,
51 debug=False, key="appserver"):76 debug=False, key="appserver"):
52 """77 """
53 Initialise the middleware for oops.78 Initialise the middleware for oops.
@@ -66,14 +91,9 @@
66 fh = debug and sys.stderr or None91 fh = debug and sys.stderr or None
67 self.serial = serializer.create(serializer_factory_name,92 self.serial = serializer.create(serializer_factory_name,
68 key, oops_dir, fh=fh)93 key, oops_dir, fh=fh)
94 self.track_completion = resolve_name(completion_tracker_name)
6995
70 def __call__(self, environ, start_response):96 def __call__(self, environ, start_response):
71 """Call do_request and pump once to start request processing."""
72 gen = self.do_request(environ, start_response)
73 gen.next() # first value yielded is meant to be discarded
74 return gen
75
76 def do_request(self, environ, start_response):
77 """97 """
78 Capture exceptions during WSGI application execution and dump98 Capture exceptions during WSGI application execution and dump
79 oopses as needed.99 oopses as needed.
@@ -123,37 +143,31 @@
123 setattr(trap_response, 'oops', oops)143 setattr(trap_response, 'oops', oops)
124144
125 metadata["request-start"] = time.time()145 metadata["request-start"] = time.time()
126 try:146
127 body = self.app(environ, trap_response)147 def on_completion(ex_type, ex_value, ex_tb):
128 yield "" # thrown away by __call__148 """Handle request completion."""
129 for chunk in body:149 if ex_type is not None:
130 yield chunk150 oops.keep()
131 except GeneratorExit:151 message = "Unhandled WSGI application exception " \
132 close = getattr(body, 'close', None)152 "(OOPS %s)" % (oops.id,)
133 if close:153 exc_info = (ex_type, ex_value, ex_tb)
134 close()
135 except:
136 oops.keep()
137 exc_info = sys.exc_info()
138 try:
139 message = "Unhandled WSGI application exception (OOPS %s)" % \
140 oops.id
141 root_logger.error(message, exc_info=exc_info)154 root_logger.error(message, exc_info=exc_info)
142 raise exc_info[0], exc_info[1], exc_info[2]155
143 finally:
144 exc_info = None
145 finally:
146 metadata["request-end"] = time.time()156 metadata["request-end"] = time.time()
147 if environ.has_key('dump-oops') and not oops.wanted:157 if environ.has_key('dump-oops') and not oops.wanted:
148 oops.keep()
149 root_logger.error("Forced dump of OOPS %s" % oops.id)
150 if oops.wanted:
151 try:158 try:
152 self.serial.dump(oops)159 self.serial.dump(oops)
153 except:160 except:
154 exc_info = sys.exc_info()161 exc_info = sys.exc_info()
155 try:162 try:
156 message = "Error when dumping OOPS %s" % oops.id163 message = "Error when dumping OOPS %s" % oops.id
157 root_logger.error(message, exc_info=exc_info)164 root_logger.error(message, exc_info=exc_info)
158 finally:165 finally:
159 exc_info = None166 exc_info = None
167
168 try:
169 body = self.app(environ, trap_response)
170 except Exception:
171 on_completion(*sys.exc_info())
172 else:
173 return self.track_completion(body, on_completion)

Subscribers

People subscribed via source and target branches