From: Ben Darnell Date: Sat, 4 May 2013 04:13:53 +0000 (-0400) Subject: Add overrideable method RequestHandler.log_exception. X-Git-Tag: v3.1.0~86 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1b4556f1507dfd955ef783d08ad201500786d62b;p=thirdparty%2Ftornado.git Add overrideable method RequestHandler.log_exception. Most apps that currently override _handle_request_exception can now use a combination of log_exception and write_error. Closes #730. --- diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index e1d71fb36..405bb0ec3 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -1120,3 +1120,57 @@ class ClearAllCookiesTest(SimpleHandlerTestCase): set_cookies = sorted(response.headers.get_list('Set-Cookie')) self.assertTrue(set_cookies[0].startswith('baz=;')) self.assertTrue(set_cookies[1].startswith('foo=;')) + + +class PermissionError(Exception): + pass + + +@wsgi_safe +class ExceptionHandlerTest(SimpleHandlerTestCase): + class Handler(RequestHandler): + def get(self): + exc = self.get_argument('exc') + if exc == 'http': + raise HTTPError(410, "no longer here") + elif exc == 'zero': + 1 / 0 + elif exc == 'permission': + raise PermissionError('not allowed') + + def write_error(self, status_code, **kwargs): + if 'exc_info' in kwargs: + typ, value, tb = kwargs['exc_info'] + if isinstance(value, PermissionError): + self.set_status(403) + self.write('PermissionError') + return + RequestHandler.write_error(self, status_code, **kwargs) + + def log_exception(self, typ, value, tb): + if isinstance(value, PermissionError): + app_log.warning('custom logging for PermissionError: %s', + value.args[0]) + else: + RequestHandler.log_exception(self, typ, value, tb) + + def test_http_error(self): + # HTTPErrors are logged as warnings with no stack trace. + # TODO: extend ExpectLog to test this more precisely + with ExpectLog(gen_log, '.*no longer here'): + response = self.fetch('/?exc=http') + self.assertEqual(response.code, 410) + + def test_unknown_error(self): + # Unknown errors are logged as errors with a stack trace. + with ExpectLog(app_log, 'Uncaught exception'): + response = self.fetch('/?exc=zero') + self.assertEqual(response.code, 500) + + def test_known_error(self): + # log_exception can override logging behavior, and write_error + # can override the response. + with ExpectLog(app_log, + 'custom logging for PermissionError: not allowed'): + response = self.fetch('/?exc=permission') + self.assertEqual(response.code, 403) diff --git a/tornado/web.py b/tornado/web.py index 7b368763a..1c1af102a 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1110,21 +1110,34 @@ class RequestHandler(object): " (" + self.request.remote_ip + ")" def _handle_request_exception(self, e): + self.log_exception(*sys.exc_info()) if isinstance(e, HTTPError): - if e.log_message: - format = "%d %s: " + e.log_message - args = [e.status_code, self._request_summary()] + list(e.args) - gen_log.warning(format, *args) if e.status_code not in httputil.responses and not e.reason: gen_log.error("Bad HTTP status code: %d", e.status_code) self.send_error(500, exc_info=sys.exc_info()) else: self.send_error(e.status_code, exc_info=sys.exc_info()) else: - app_log.error("Uncaught exception %s\n%r", self._request_summary(), - self.request, exc_info=True) self.send_error(500, exc_info=sys.exc_info()) + def log_exception(self, typ, value, tb): + """Override to customize logging of uncaught exceptions. + + By default logs instances of `HTTPError` as warnings without + stack traces (on the ``tornado.general`` logger), and all + other exceptions as errors with stack traces (on the + ``tornado.application`` logger). + """ + if isinstance(value, HTTPError): + if value.log_message: + format = "%d %s: " + value.log_message + args = ([value.status_code, self._request_summary()] + + list(value.args)) + gen_log.warning(format, *args) + else: + app_log.error("Uncaught exception %s\n%r", self._request_summary(), + self.request, exc_info=(typ, value, tb)) + def _ui_module(self, name, module): def render(*args, **kwargs): if not hasattr(self, "_active_modules"):