summaryrefslogtreecommitdiffstats
path: root/main/log.py
blob: 63634874361dedcf43a94a7fbe2dcc0e99e2c3a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# Derived from Django snippets: http://djangosnippets.org/snippets/2242/
from collections import OrderedDict
from datetime import datetime, timedelta
from hashlib import md5
import traceback
from pytz import utc


class LimitedSizeDict(OrderedDict):
    def __init__(self, *args, **kwargs):
        self.size_limit = kwargs.pop('size', None)
        if self.size_limit == 0:
            self.size_limit = None
        if self.size_limit and self.size_limit < 0:
            raise Exception('Invalid size specified')
        super(LimitedSizeDict, self).__init__(*args, **kwargs)
        self.check_item_limits()

    def __setitem__(self, key, value):
        # delete and add to ensure it ends up at the end of the linked list
        if key in self:
            super(LimitedSizeDict, self).__delitem__(key)
        super(LimitedSizeDict, self).__setitem__(key, value)
        self.check_item_limits()

    def check_item_limits(self):
        if self.size_limit is None:
            return
        while len(self) > self.size_limit:
            self.popitem(last=False)


class RateLimitFilter(object):
    def __init__(self, name='', rate=10, prefix='error_rate', max_keys=100):
        # delayed import otherwise we have a circular dep when setting up
        # the logging config: settings -> logging -> cache -> settings
        self.cache_module = __import__('django.core.cache', fromlist=['cache'])
        self.errors = LimitedSizeDict(size=max_keys)
        self.rate = rate
        self.prefix = prefix

    def filter(self, record):
        if self.rate == 0:
            # rate == 0 means totally unfiltered
            return True

        trace = '\n'.join(traceback.format_exception(*record.exc_info))
        key = md5(trace).hexdigest()
        duplicate = False
        cache = self.cache_module.cache

        # Test if the cache works
        try:
            cache.set(self.prefix, 1, 300)
            use_cache = (cache.get(self.prefix) == 1)
        except:
            use_cache = False

        if use_cache:
            cache_key = '%s_%s' % (self.prefix, key)
            duplicate = (cache.get(cache_key) == 1)
            cache.set(cache_key, 1, self.rate)
        else:
            now = datetime.utcnow().replace(tzinfo=utc)
            min_date = now - timedelta(seconds=self.rate)
            duplicate = (key in self.errors and self.errors[key] >= min_date)
            self.errors[key] = now

        return not duplicate

# vim: set ts=4 sw=4 et: