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)
" (" + 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"):