Log errors to Humbug, too.

(imported from commit 2547625135568f3ea004bf4287471a82bc0a4f38)
This commit is contained in:
Luke Faraone 2012-12-06 16:00:34 -05:00
parent adf289c9df
commit a604183c5b
3 changed files with 94 additions and 13 deletions

View File

@ -4,8 +4,7 @@ from datetime import datetime, timedelta
# Adapted http://djangosnippets.org/snippets/2242/ by user s29 (October 25, 2010)
class RateLimitFilter(object):
class _RateLimitFilter(object):
last_error = 0
def filter(self, record):
@ -14,7 +13,8 @@ class RateLimitFilter(object):
# Track duplicate errors
duplicate = False
rate = getattr(settings, 'ERROR_RATE_LIMIT', 600) # seconds
rate = getattr(settings, '%s_LIMIT' % self.__class__.__name__.upper(),
600) # seconds
if rate > 0:
# Test if the cache works
try:
@ -24,8 +24,9 @@ class RateLimitFilter(object):
use_cache = False
if use_cache:
duplicate = cache.get('ERROR_RATE') == 1
cache.set('ERROR_RATE', 1, rate)
key = self.__class__.__name__.upper()
duplicate = cache.get(key) == 1
cache.set(key, 1, rate)
else:
min_date = datetime.now() - timedelta(seconds=rate)
duplicate = (self.last_error >= min_date)
@ -33,3 +34,9 @@ class RateLimitFilter(object):
self.last_error = datetime.now()
return not duplicate
class HumbugLimiter(_RateLimitFilter):
pass
class EmailLimiter(_RateLimitFilter):
pass

View File

@ -154,7 +154,6 @@ if deployed:
'LOCATION': '127.0.0.1:11211',
'TIMEOUT': 3600
} }
error_filters = ['ratelimit']
else:
CACHES = { 'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
@ -164,9 +163,6 @@ else:
'MAX_ENTRIES': 100000
}
} }
error_filters = []
ERROR_RATE_LIMIT=600
LOGGING = {
'version': 1,
@ -177,11 +173,23 @@ LOGGING = {
}
},
'filters': {
'ratelimit': {
'()': 'humbug.ratelimit.RateLimitFilter',
'HumbugLimiter': {
'()': 'humbug.ratelimit.HumbugLimiter',
},
'EmailLimiter': {
'()': 'humbug.ratelimit.EmailLimiter',
},
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'handlers': {
'inapp': {
'level': 'ERROR',
'class': 'zephyr.handlers.AdminHumbugHandler',
'filters': ['HumbugLimiter', 'require_debug_false'],
'formatter': 'default'
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
@ -196,12 +204,12 @@ LOGGING = {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'filters': error_filters,
'filters': ['EmailLimiter', 'require_debug_false'],
},
},
'loggers': {
'': {
'handlers': ['console', 'file', 'mail_admins'],
'handlers': ['inapp', 'console', 'file', 'mail_admins'],
'level': 'INFO'
}
}

66
zephyr/handlers.py Normal file
View File

@ -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]