12
12
from bzrlib import errors, lru_cache, urlutils
13
13
from bzrlib.transport import get_transport
15
14
from loggerhead.apps import favicon_app, static_app
16
15
from loggerhead.apps.branch import BranchWSGIApp
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
21
19
from paste import httpserver
22
20
from paste.fileapp import DataApp
21
from paste.httpexceptions import (
23
26
from paste.request import construct_url, parse_querystring, path_info_pop
24
from paste.httpexceptions import (
25
HTTPMovedPermanently, HTTPNotFound, HTTPUnauthorized)
27
28
from canonical.config import config
28
29
from canonical.launchpad.xmlrpc import faults
234
235
lp_server.stop_server()
237
def make_oops_logging_exception_hook(error_utility, request):
238
"""Make a hook for logging OOPSes."""
240
error_utility.raising(sys.exc_info(), request)
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>'''
268
_error_status = '500 Internal Server Error'
269
_error_headers = [('Content-Type', 'text/html')]
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
277
Used by oops_middleware.
280
def __init__(self, start_response):
281
self._real_start_response = start_response
282
self.response_start = None
283
self._write_callable = None
286
def body_started(self):
287
return self._write_callable is not None
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
293
self.response_start = (status, headers)
294
if exc_info is not None:
295
self.response_start += (exc_info,)
296
return self.write_wrapper
298
def ensure_started(self):
299
if not self.body_started and self.response_start is not None:
302
def _really_start(self):
303
self._write_callable = self._real_start_response(*self.response_start)
305
def write_wrapper(self, data):
306
self.ensure_started()
307
self._write_callable(data)
309
def generate_oops(self, environ, error_utility):
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).
315
oopsid = report_oops(environ, error_utility)
316
if self.body_started:
318
write = self.start_response(_error_status, _error_headers)
319
write(_oops_html_template % {'oopsid': oopsid})
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
334
262
def oops_middleware(app):
335
263
"""Middleware to log an OOPS if the request fails.
338
266
a basic HTML error page with the OOPS ID to the user (and status code 500).
340
268
error_utility = make_error_utility()
341
def wrapped_app(environ, start_response):
342
wrapped = WrappedStartResponse(start_response)
344
# Start processing this request, build the app
345
app_iter = iter(app(environ, wrapped.start_response))
346
# Start yielding the response
350
data = app_iter.next()
351
except StopIteration:
353
wrapped.ensure_started()
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>'),
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>')))
375
error_page_sent = wrapped.generate_oops(environ, error_utility)
378
# Could not send error page to user, so... just give up.
269
return oops_wsgi.make_app(app, error_utility._oops_config,
270
template=_oops_html_template)