mirror of https://github.com/zulip/zulip.git
Log errors to Humbug, too.
(imported from commit 2547625135568f3ea004bf4287471a82bc0a4f38)
This commit is contained in:
parent
adf289c9df
commit
a604183c5b
|
@ -4,8 +4,7 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
# Adapted http://djangosnippets.org/snippets/2242/ by user s29 (October 25, 2010)
|
# Adapted http://djangosnippets.org/snippets/2242/ by user s29 (October 25, 2010)
|
||||||
|
|
||||||
class RateLimitFilter(object):
|
class _RateLimitFilter(object):
|
||||||
|
|
||||||
last_error = 0
|
last_error = 0
|
||||||
|
|
||||||
def filter(self, record):
|
def filter(self, record):
|
||||||
|
@ -14,7 +13,8 @@ class RateLimitFilter(object):
|
||||||
|
|
||||||
# Track duplicate errors
|
# Track duplicate errors
|
||||||
duplicate = False
|
duplicate = False
|
||||||
rate = getattr(settings, 'ERROR_RATE_LIMIT', 600) # seconds
|
rate = getattr(settings, '%s_LIMIT' % self.__class__.__name__.upper(),
|
||||||
|
600) # seconds
|
||||||
if rate > 0:
|
if rate > 0:
|
||||||
# Test if the cache works
|
# Test if the cache works
|
||||||
try:
|
try:
|
||||||
|
@ -24,8 +24,9 @@ class RateLimitFilter(object):
|
||||||
use_cache = False
|
use_cache = False
|
||||||
|
|
||||||
if use_cache:
|
if use_cache:
|
||||||
duplicate = cache.get('ERROR_RATE') == 1
|
key = self.__class__.__name__.upper()
|
||||||
cache.set('ERROR_RATE', 1, rate)
|
duplicate = cache.get(key) == 1
|
||||||
|
cache.set(key, 1, rate)
|
||||||
else:
|
else:
|
||||||
min_date = datetime.now() - timedelta(seconds=rate)
|
min_date = datetime.now() - timedelta(seconds=rate)
|
||||||
duplicate = (self.last_error >= min_date)
|
duplicate = (self.last_error >= min_date)
|
||||||
|
@ -33,3 +34,9 @@ class RateLimitFilter(object):
|
||||||
self.last_error = datetime.now()
|
self.last_error = datetime.now()
|
||||||
|
|
||||||
return not duplicate
|
return not duplicate
|
||||||
|
|
||||||
|
class HumbugLimiter(_RateLimitFilter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class EmailLimiter(_RateLimitFilter):
|
||||||
|
pass
|
||||||
|
|
|
@ -154,7 +154,6 @@ if deployed:
|
||||||
'LOCATION': '127.0.0.1:11211',
|
'LOCATION': '127.0.0.1:11211',
|
||||||
'TIMEOUT': 3600
|
'TIMEOUT': 3600
|
||||||
} }
|
} }
|
||||||
error_filters = ['ratelimit']
|
|
||||||
else:
|
else:
|
||||||
CACHES = { 'default': {
|
CACHES = { 'default': {
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
@ -164,9 +163,6 @@ else:
|
||||||
'MAX_ENTRIES': 100000
|
'MAX_ENTRIES': 100000
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
error_filters = []
|
|
||||||
|
|
||||||
ERROR_RATE_LIMIT=600
|
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
|
@ -177,11 +173,23 @@ LOGGING = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'filters': {
|
'filters': {
|
||||||
'ratelimit': {
|
'HumbugLimiter': {
|
||||||
'()': 'humbug.ratelimit.RateLimitFilter',
|
'()': 'humbug.ratelimit.HumbugLimiter',
|
||||||
|
},
|
||||||
|
'EmailLimiter': {
|
||||||
|
'()': 'humbug.ratelimit.EmailLimiter',
|
||||||
|
},
|
||||||
|
'require_debug_false': {
|
||||||
|
'()': 'django.utils.log.RequireDebugFalse',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'handlers': {
|
'handlers': {
|
||||||
|
'inapp': {
|
||||||
|
'level': 'ERROR',
|
||||||
|
'class': 'zephyr.handlers.AdminHumbugHandler',
|
||||||
|
'filters': ['HumbugLimiter', 'require_debug_false'],
|
||||||
|
'formatter': 'default'
|
||||||
|
},
|
||||||
'console': {
|
'console': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.StreamHandler',
|
'class': 'logging.StreamHandler',
|
||||||
|
@ -196,12 +204,12 @@ LOGGING = {
|
||||||
'mail_admins': {
|
'mail_admins': {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
'class': 'django.utils.log.AdminEmailHandler',
|
'class': 'django.utils.log.AdminEmailHandler',
|
||||||
'filters': error_filters,
|
'filters': ['EmailLimiter', 'require_debug_false'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'': {
|
'': {
|
||||||
'handlers': ['console', 'file', 'mail_admins'],
|
'handlers': ['inapp', 'console', 'file', 'mail_admins'],
|
||||||
'level': 'INFO'
|
'level': 'INFO'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.views.debug import get_exception_reporter_filter
|
||||||
|
|
||||||
|
|
||||||
|
class AdminHumbugHandler(logging.Handler):
|
||||||
|
"""An exception log handler that Humbugs log entries to the Humbug realm.
|
||||||
|
|
||||||
|
If the request is passed as the first argument to the log record,
|
||||||
|
request data will be provided in the email report.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# adapted in part from django/utils/log.py
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
logging.Handler.__init__(self)
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
# We have to defer imports to avoid circular imports in settings.py.
|
||||||
|
from zephyr.models import Message, UserProfile, Recipient, \
|
||||||
|
create_stream_if_needed, get_client, do_send_message
|
||||||
|
from django.conf import settings
|
||||||
|
message = Message()
|
||||||
|
message.sender = UserProfile.objects.get(user__email="humbug+errors@humbughq.com")
|
||||||
|
message.recipient = Recipient.objects.get(type_id=create_stream_if_needed(
|
||||||
|
message.sender.realm, "devel").id, type=Recipient.STREAM)
|
||||||
|
message.pub_date = now()
|
||||||
|
message.sending_client = get_client("Internal")
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = record.request
|
||||||
|
subject = '%s (%s IP): %s' % (
|
||||||
|
record.levelname,
|
||||||
|
(request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS
|
||||||
|
and 'internal' or 'EXTERNAL'),
|
||||||
|
record.getMessage()
|
||||||
|
)
|
||||||
|
filter = get_exception_reporter_filter(request)
|
||||||
|
request_repr = filter.get_request_repr(request)
|
||||||
|
except Exception:
|
||||||
|
subject = '%s: %s' % (
|
||||||
|
record.levelname,
|
||||||
|
record.getMessage()
|
||||||
|
)
|
||||||
|
request = None
|
||||||
|
request_repr = "Request repr() unavailable."
|
||||||
|
message.subject = self.format_subject(subject)
|
||||||
|
|
||||||
|
if record.exc_info:
|
||||||
|
stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
|
||||||
|
else:
|
||||||
|
stack_trace = 'No stack trace available'
|
||||||
|
|
||||||
|
message.content = "~~~~ pytb\n%s\n\n%s\n~~~~" % (stack_trace, request_repr)
|
||||||
|
do_send_message(message)
|
||||||
|
|
||||||
|
def format_subject(self, subject):
|
||||||
|
"""
|
||||||
|
Escape CR and LF characters, and limit length to 60 characters.
|
||||||
|
"""
|
||||||
|
formatted_subject = subject.replace('\n', '\\n').replace('\r', '\\r')
|
||||||
|
return formatted_subject[:60]
|
||||||
|
|
Loading…
Reference in New Issue