]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add overrideable method RequestHandler.log_exception.
authorBen Darnell <ben@bendarnell.com>
Sat, 4 May 2013 04:13:53 +0000 (00:13 -0400)
committerBen Darnell <ben@bendarnell.com>
Sat, 4 May 2013 04:14:50 +0000 (00:14 -0400)
Most apps that currently override _handle_request_exception can now
use a combination of log_exception and write_error.

Closes #730.

tornado/test/web_test.py
tornado/web.py

index e1d71fb36dec6b4943b7bac7b7e57a18551a8004..405bb0ec3e2167dbf56953cf2098054adcde14f7 100644 (file)
@@ -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)
index 7b368763ad4e76120b36e7bac2d1dbd7cd5fdaa7..1c1af102a9dc981c56dd21251244a60159d49a95 100644 (file)
@@ -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"):