Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Imports
3from django.conf import settings
4from django.core.cache import cache
5from django import http
6from django.http import HttpResponse
7from django.views import debug
8from hashlib import sha1
9from importlib import import_module
10import sys
12# Exports
14___all__ = (
15 "ErrorCaptureMiddleware",
16)
18# Constants
20ERROR_CAPTURE_DEBUG = getattr(settings, "SUPERDJANGO_ERROR_CAPTURE_DEBUG", False)
21ERROR_CAPTURE_REPEAT_SECONDS = getattr(settings, "SUPERDJANGO_ERROR_CAPTURE_REPEAT_SECONDS", 3600)
22ERROR_CAPTURE_HANDLERS = getattr(settings, "SUPERDJANGO_ERROR_CAPTURE_HANDLERS", list())
24# Classes
27class ErrorCaptureMiddleware(object):
28 """Middleware for capturing exceptions."""
30 traceback = __import__('traceback')
32 def __init__(self, get_response):
33 self.get_response = get_response
35 def __call__(self, request):
36 return self.get_response(request)
38 def capture_exception(self, request, exception):
39 """Runs the exception capture process.
41 :param request: The current HTTP request.
43 :param exception: The exception being raised.
45 :rtype: BaseType[HttpResponse]
47 """
48 # Just raise if it's a 404.
49 if isinstance(exception, http.Http404):
50 raise exception
52 # Get content and info.
53 content = self.traceback.format_exc()
54 info = sys.exc_info()
56 # Don't handle in DEBUG mode unless specifically requested.
57 if settings.DEBUG and not ERROR_CAPTURE_DEBUG:
58 return debug.technical_500_response(request, *info)
60 # Hash is used to identify the exception in cache.
61 _hash = sha1()
62 _hash.update(content.encode("utf-8"))
63 digest = _hash.hexdigest()
65 # Don't do anything if the exception has been handled previously.
66 if cache.get(digest) is not None:
67 return
69 # Cached to avoid repetition.
70 cache.set(digest, content, ERROR_CAPTURE_REPEAT_SECONDS)
72 # Initialize the report to be passed to the handlers.
73 report = debug.ExceptionReporter(request, *info)
75 # Iterate through handlers. The last result is returned if it's a response.
76 context = dict()
77 result = None
78 for handler in ERROR_CAPTURE_HANDLERS:
79 callback_class = self._get_handler_instance(handler)
80 if callback_class is None:
81 continue
83 if result and type(result) is dict:
84 context.update(result)
86 instance = callback_class()
87 result = instance(report, context=context)
89 if result and isinstance(result, HttpResponse):
90 return result
92 return debug.technical_500_response(request, *info)
94 def process_exception(self, request, exception):
95 """Process the exception."""
96 return self.capture_exception(request, exception)
98 # noinspection PyMethodMayBeStatic
99 def _get_handler_instance(self, dotted):
100 """Get the handler instance from the given dotted path.
102 :param dotted: The dotted path to the handler.
103 :type dotted: str
105 :returns: The instance or ``None`` if it could not be loaded.
107 """
108 class_name = dotted.split(".")[-1]
109 dotted = dotted.replace(".%s" % class_name, "")
110 try:
111 module = import_module(dotted)
112 try:
113 return getattr(module, class_name)
114 except AttributeError:
115 return None
116 except ImportError:
117 return None