Commits

Andriy Kornatskyy committed a5b09c1

Added logger and extra provider to HTTPErrorMiddleware.

  • Participants
  • Parent commits 69cfba1

Comments (0)

Files changed (2)

src/wheezy/web/middleware/errors.py

 """ ``errors`` module.
 """
 
-from traceback import print_exc
+import sys
+
+from traceback import format_exception_only
 
 from wheezy.core.collections import defaultdict
 from wheezy.http.response import internal_error
 class HTTPErrorMiddleware(object):
     """ http error middleware
     """
-    __slots__ = ('error_mapping')
 
-    def __init__(self, error_mapping):
+    def __init__(self, error_mapping, logger, extra_provider=None):
         assert error_mapping is not None
         self.error_mapping = error_mapping
+        self.logger = logger
+        self.extra_provider = extra_provider
 
     def __call__(self, request, following):
         assert following is not None
         except (KeyboardInterrupt, SystemExit, MemoryError):
             raise
         except Exception:
-            print_exc()
+            exc_info = sys.exc_info()
+            extra = self.extra_provider and self.extra_provider(request) or {}
+            self.logger.error(
+                ''.join(format_exception_only(*exc_info[:2])).strip('\n'),
+                exc_info=exc_info,
+                extra=extra)
+            sys.exc_clear()
             response = internal_error()
         status_code = response.status_code
         if status_code >= 400:
 def http_error_middleware_factory(options):
     """ HTTP error middleware factory.
     """
-    path_for = options['path_for']
+    import logging
     if 'http_errors' in options:
+        path_for = options['path_for']
         error_mapping = options['http_errors']
         assert isinstance(error_mapping, defaultdict)
         assert path_for(error_mapping.default_factory()) is not None
             assert path_for(route_name) is not None
     else:
         error_mapping = defaultdict(str)
-    return HTTPErrorMiddleware(error_mapping=error_mapping)
+    if 'http_errors_logger' in options:
+        logger = options['http_errors_logger']
+    else:
+        logger = logging.getLogger('unhandled')
+        if not logger.handlers:
+            logger.setLevel(logging.ERROR)
+            logger.addHandler(logging.StreamHandler(sys.stderr))
+    return HTTPErrorMiddleware(
+        error_mapping,
+        logger,
+        options.get('http_errors_extra_provider', None),
+    )

src/wheezy/web/middleware/tests/test_errors.py

         mock_following = Mock(return_value=None)
         middleware = HTTPErrorMiddleware({
             404: 'http404'
-        })
+        }, None)
         response = middleware(mock_request, mock_following)
         assert 404 == response.status_code
 
         mock_response = Mock()
         mock_response.status_code = 200
         mock_following = Mock(return_value=mock_response)
-        middleware = HTTPErrorMiddleware({})
+        middleware = HTTPErrorMiddleware({}, None)
         response = middleware(mock_request, mock_following)
         assert 200 == response.status_code
 
         mock_request.environ = {
             'route_args': {'route_name': 'http500'}
         }
+        mock_logger = Mock()
         mock_following = Mock(side_effect=ValueError)
         middleware = HTTPErrorMiddleware({
             500: 'http500'
-        })
+        }, mock_logger)
         response = middleware(mock_request, mock_following)
+
         assert 500 == response.status_code
+        assert mock_logger.error.called
+
+    def test_following_raises_error_extra_info(self):
+        """ Ensure extra provider is called.
+        """
+        from wheezy.web.middleware.errors import HTTPErrorMiddleware
+        mock_request = Mock()
+        mock_request.environ = {
+            'route_args': {'route_name': 'http500'}
+        }
+        mock_logger = Mock()
+        mock_extra_provider = Mock()
+        mock_following = Mock(side_effect=ValueError)
+        middleware = HTTPErrorMiddleware({
+            500: 'http500'
+        }, mock_logger, mock_extra_provider)
+        response = middleware(mock_request, mock_following)
+
+        assert 500 == response.status_code
+        mock_extra_provider.assert_called_once_with(mock_request)
+        assert mock_logger.error.called
 
     def test_following_error_pass_through(self):
         """ The following middleware raises error that pass
         }
         for error in [KeyboardInterrupt, SystemExit, MemoryError]:
             mock_following = Mock(side_effect=error)
-            middleware = HTTPErrorMiddleware({})
+            middleware = HTTPErrorMiddleware({}, None)
             self.assertRaises(
                 error,
                 lambda: middleware(mock_request, mock_following))
         mock_following = Mock(return_value=None)
         middleware = HTTPErrorMiddleware({
             404: 'http404'
-        })
+        }, None)
         response = middleware(mock_request, mock_following)
         assert 302 == response.status_code
 
         """ http_errors options is undefined.
         """
         from wheezy.web.middleware.errors import http_error_middleware_factory
-        mock_path_for = Mock()
-        middleware = http_error_middleware_factory({
-            'path_for': mock_path_for
-        })
+        middleware = http_error_middleware_factory({})
         assert middleware
 
     def test_http_errors(self):
             })
         })
         assert middleware
+
+    def test_logger_defined(self):
+        """ logger is available in options.
+        """
+        from wheezy.web.middleware.errors import http_error_middleware_factory
+        middleware = http_error_middleware_factory({
+            'http_errors_logger': Mock()
+        })
+        assert middleware