# Imports
from django.conf import settings
from django.core.cache import cache
from django import http
from django.http import HttpResponse
from django.views import debug
from hashlib import sha1
from importlib import import_module
import sys
# Exports
___all__ = (
"ErrorCaptureMiddleware",
)
# Constants
ERROR_CAPTURE_DEBUG = getattr(settings, "SUPERDJANGO_ERROR_CAPTURE_DEBUG", False)
ERROR_CAPTURE_REPEAT_SECONDS = getattr(settings, "SUPERDJANGO_ERROR_CAPTURE_REPEAT_SECONDS", 3600)
ERROR_CAPTURE_HANDLERS = getattr(settings, "SUPERDJANGO_ERROR_CAPTURE_HANDLERS", list())
# Classes
[docs]class ErrorCaptureMiddleware(object):
"""Middleware for capturing exceptions."""
traceback = __import__('traceback')
[docs] def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
[docs] def capture_exception(self, request, exception):
"""Runs the exception capture process.
:param request: The current HTTP request.
:param exception: The exception being raised.
:rtype: BaseType[HttpResponse]
"""
# Just raise if it's a 404.
if isinstance(exception, http.Http404):
raise exception
# Get content and info.
content = self.traceback.format_exc()
info = sys.exc_info()
# Don't handle in DEBUG mode unless specifically requested.
if settings.DEBUG and not ERROR_CAPTURE_DEBUG:
return debug.technical_500_response(request, *info)
# Hash is used to identify the exception in cache.
_hash = sha1()
_hash.update(content.encode("utf-8"))
digest = _hash.hexdigest()
# Don't do anything if the exception has been handled previously.
if cache.get(digest) is not None:
return
# Cached to avoid repetition.
cache.set(digest, content, ERROR_CAPTURE_REPEAT_SECONDS)
# Initialize the report to be passed to the handlers.
report = debug.ExceptionReporter(request, *info)
# Iterate through handlers. The last result is returned if it's a response.
context = dict()
result = None
for handler in ERROR_CAPTURE_HANDLERS:
callback_class = self._get_handler_instance(handler)
if callback_class is None:
continue
if result and type(result) is dict:
context.update(result)
instance = callback_class()
result = instance(report, context=context)
if result and isinstance(result, HttpResponse):
return result
return debug.technical_500_response(request, *info)
[docs] def process_exception(self, request, exception):
"""Process the exception."""
return self.capture_exception(request, exception)
# noinspection PyMethodMayBeStatic
def _get_handler_instance(self, dotted):
"""Get the handler instance from the given dotted path.
:param dotted: The dotted path to the handler.
:type dotted: str
:returns: The instance or ``None`` if it could not be loaded.
"""
class_name = dotted.split(".")[-1]
dotted = dotted.replace(".%s" % class_name, "")
try:
module = import_module(dotted)
try:
return getattr(module, class_name)
except AttributeError:
return None
except ImportError:
return None