~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/launchpad_loggerhead/app.py

  • Committer: Robert Collins
  • Date: 2011-08-18 03:32:33 UTC
  • mto: This revision was merged to the branch mainline in revision 13728.
  • Revision ID: robertc@robertcollins.net-20110818033233-u345xu9cl57t2pkw
Migrate the launchpad_loggerhead implementation of oops_middleware to that within oops_wsgi.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
 
12
12
from bzrlib import errors, lru_cache, urlutils
13
13
from bzrlib.transport import get_transport
14
 
 
15
14
from loggerhead.apps import favicon_app, static_app
16
15
from loggerhead.apps.branch import BranchWSGIApp
17
 
 
 
16
import oops_wsgi
 
17
from openid.consumer.consumer import CANCEL, Consumer, FAILURE, SUCCESS
18
18
from openid.extensions.sreg import SRegRequest, SRegResponse
19
 
from openid.consumer.consumer import CANCEL, Consumer, FAILURE, SUCCESS
20
 
 
21
19
from paste import httpserver
22
20
from paste.fileapp import DataApp
 
21
from paste.httpexceptions import (
 
22
    HTTPMovedPermanently,
 
23
    HTTPNotFound,
 
24
    HTTPUnauthorized,
 
25
    )
23
26
from paste.request import construct_url, parse_querystring, path_info_pop
24
 
from paste.httpexceptions import (
25
 
    HTTPMovedPermanently, HTTPNotFound, HTTPUnauthorized)
26
27
 
27
28
from canonical.config import config
28
29
from canonical.launchpad.xmlrpc import faults
234
235
            lp_server.stop_server()
235
236
 
236
237
 
237
 
def make_oops_logging_exception_hook(error_utility, request):
238
 
    """Make a hook for logging OOPSes."""
239
 
    def log_oops():
240
 
        error_utility.raising(sys.exc_info(), request)
241
 
    return log_oops
242
 
 
243
 
 
244
238
def make_error_utility():
245
239
    """Make an error utility for logging errors from codebrowse."""
246
240
    error_utility = ErrorReportingUtility()
265
259
</p></body></html>'''
266
260
 
267
261
 
268
 
_error_status = '500 Internal Server Error'
269
 
_error_headers = [('Content-Type', 'text/html')]
270
 
 
271
 
 
272
 
class WrappedStartResponse(object):
273
 
    """Wraps start_response (from a WSGI request) to keep track of whether
274
 
    start_response was called (and whether the callable it returns has been
275
 
    called).
276
 
 
277
 
    Used by oops_middleware.
278
 
    """
279
 
 
280
 
    def __init__(self, start_response):
281
 
        self._real_start_response = start_response
282
 
        self.response_start = None
283
 
        self._write_callable = None
284
 
 
285
 
    @property
286
 
    def body_started(self):
287
 
        return self._write_callable is not None
288
 
 
289
 
    def start_response(self, status, headers, exc_info=None):
290
 
        # Just keep a note of the values we were called with for now.  We don't
291
 
        # need to invoke the real start_response until the response body
292
 
        # starts.
293
 
        self.response_start = (status, headers)
294
 
        if exc_info is not None:
295
 
            self.response_start += (exc_info,)
296
 
        return self.write_wrapper
297
 
 
298
 
    def ensure_started(self):
299
 
        if not self.body_started and self.response_start is not None:
300
 
            self._really_start()
301
 
 
302
 
    def _really_start(self):
303
 
        self._write_callable = self._real_start_response(*self.response_start)
304
 
 
305
 
    def write_wrapper(self, data):
306
 
        self.ensure_started()
307
 
        self._write_callable(data)
308
 
 
309
 
    def generate_oops(self, environ, error_utility):
310
 
        """Generate an OOPS.
311
 
 
312
 
        :returns: True if the error page was sent to the user, and False if it
313
 
            couldn't (i.e. if the response body was already started).
314
 
        """
315
 
        oopsid = report_oops(environ, error_utility)
316
 
        if self.body_started:
317
 
            return False
318
 
        write = self.start_response(_error_status, _error_headers)
319
 
        write(_oops_html_template % {'oopsid': oopsid})
320
 
        return True
321
 
 
322
 
 
323
 
def report_oops(environ, error_utility):
324
 
    # XXX: We should capture more per-request information to include in
325
 
    # the OOPS here, e.g. duration, user, etc.  But even an OOPS with
326
 
    # just a traceback and URL is better than nothing.
327
 
    #   - Andrew Bennetts, 2010-07-27.
328
 
    request = ScriptRequest(
329
 
        [], URL=construct_url(environ))
330
 
    error_utility.raising(sys.exc_info(), request)
331
 
    return request.oopsid
332
 
 
333
 
 
334
262
def oops_middleware(app):
335
263
    """Middleware to log an OOPS if the request fails.
336
264
 
338
266
    a basic HTML error page with the OOPS ID to the user (and status code 500).
339
267
    """
340
268
    error_utility = make_error_utility()
341
 
    def wrapped_app(environ, start_response):
342
 
        wrapped = WrappedStartResponse(start_response)
343
 
        try:
344
 
            # Start processing this request, build the app
345
 
            app_iter = iter(app(environ, wrapped.start_response))
346
 
            # Start yielding the response
347
 
            stopping = False
348
 
            while not stopping:
349
 
                try:
350
 
                    data = app_iter.next()
351
 
                except StopIteration:
352
 
                    stopping = True
353
 
                wrapped.ensure_started()
354
 
                if not stopping:
355
 
                    yield data
356
 
        except httpserver.SocketErrors, e:
357
 
            # The Paste WSGIHandler suppresses these exceptions.
358
 
            # Generally it means something like 'EPIPE' because the
359
 
            # connection was closed. We don't want to generate an OOPS
360
 
            # just because the connection was closed prematurely.
361
 
            logger = logging.getLogger('lp-loggerhead')
362
 
            logger.info('Caught socket exception from %s: %s %s'
363
 
                        % (environ.get('REMOTE_ADDR', '<unknown>'),
364
 
                           e.__class__, e,))
365
 
            return
366
 
        except GeneratorExit, e:
367
 
            # This generally means a client closed early during a streaming
368
 
            # body. Nothing to worry about. GeneratorExit doesn't usually have
369
 
            # any context associated with it, so not worth printing to the log.
370
 
            logger = logging.getLogger('lp-loggerhead')
371
 
            logger.info('Caught GeneratorExit from %s'
372
 
                        % (environ.get('REMOTE_ADDR', '<unknown>')))
373
 
            return
374
 
        except:
375
 
            error_page_sent = wrapped.generate_oops(environ, error_utility)
376
 
            if error_page_sent:
377
 
                return
378
 
            # Could not send error page to user, so... just give up.
379
 
            raise
380
 
    return wrapped_app
 
269
    return oops_wsgi.make_app(app, error_utility._oops_config,
 
270
            template=_oops_html_template)