Code

Ticket #11565: error_email_rate_limit_v2.diff

File error_email_rate_limit_v2.diff, 3.6 KB (added by simon29, 3 years ago)

Falls back to memory if cache isn't available

Line 
1Index: core/handlers/base.py
2===================================================================
3--- core/handlers/base.py       (revision 13296)
4+++ core/handlers/base.py       (working copy)
5@@ -1,5 +1,11 @@
6+try:
7+    from hashlib import md5
8+except ImportError:
9+    from md5 import md5
10 import sys
11+from datetime import datetime, timedelta
12 
13+from django.core.cache import cache                               
14 from django import http
15 from django.core import signals
16 from django.utils.encoding import force_unicode
17@@ -13,6 +19,8 @@
18         http.fix_IE_for_attach,
19         http.fix_IE_for_vary,
20     ]
21+   
22+    _errors = {}
23 
24     def __init__(self):
25         self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
26@@ -166,13 +174,52 @@
27             return debug.technical_500_response(request, *exc_info)
28 
29         # When DEBUG is False, send an error message to the admins.
30-        subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
31-        try:
32-            request_repr = repr(request)
33-        except:
34-            request_repr = "Request repr() unavailable"
35-        message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr)
36-        mail_admins(subject, message, fail_silently=True)
37+       
38+        # Where settings.ERROR_EMAIL_RATE_LIMIT > 0, limit to one duplicate
39+        # email per ERROR_EMAIL_RATE_LIMIT seconds. Use cache if available,
40+        # otherwise use local memory, limited to ERROR_EMAIL_KEY_LIMIT.
41+        # If using cache, prefix keys with ERROR_EMAIL_CACHE_PREFIX.
42+       
43+        # Note: none of the above settings are required!
44+       
45+        tb = self._get_traceback(exc_info)
46+       
47+        # Track duplicate errors
48+        duplicate = False
49+        rate = getattr(settings, 'ERROR_EMAIL_RATE_LIMIT', 0)  # seconds
50+        if rate > 0:
51+            key = md5(tb).hexdigest()
52+            prefix = getattr(settings, 'ERROR_EMAIL_CACHE_PREFIX', 'ERROR_EMAIL')
53+            # Test the cache works
54+            cache_key = '%s_%s' % (prefix, key)
55+            try:
56+                cache.set(prefix, 1, 1)
57+                use_cache = cache.get(prefix) == 1
58+            except:
59+                use_cache = False
60+            if use_cache:
61+                duplicate = cache.get(cache_key) == 1
62+                cache.set(cache_key, 1, rate)
63+            else:
64+                min_date = datetime.now() - timedelta(seconds=rate)
65+                max_keys = getattr(settings, 'ERROR_EMAIL_KEY_LIMIT', 100)
66+                duplicate = (key in self._errors and self._errors[key] >= min_date)
67+                self._errors = dict(filter(lambda x: x[1] >= min_date,
68+                                          sorted(self._errors.items(),
69+                                                 key=lambda x: x[1]))[0-max_keys:])
70+                if not duplicate:
71+                    self._errors[key] = datetime.now()
72+                   
73+        # Send the error
74+        if rate == 0 or not duplicate:
75+            subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
76+            try:
77+                request_repr = repr(request)
78+            except:
79+                request_repr = "Request repr() unavailable"
80+            message = "%s\n\n%s" % (tb, request_repr)
81+            mail_admins(subject, message, fail_silently=True)
82+
83         # If Http500 handler is not installed, re-raise last exception
84         if resolver.urlconf_module is None:
85             raise exc_info[1], None, exc_info[2]