diff --git a/humbug/settings.py b/humbug/settings.py index e4f93395b3..c922852652 100644 --- a/humbug/settings.py +++ b/humbug/settings.py @@ -237,7 +237,7 @@ LOGGING = { }, 'mail_admins': { 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', + 'class': 'zephyr.handlers.HumbugAdminEmailHandler', 'filters': ['EmailLimiter', 'require_debug_false'], }, }, diff --git a/zephyr/handlers.py b/zephyr/handlers.py index e421bf131a..a535c14eec 100644 --- a/zephyr/handlers.py +++ b/zephyr/handlers.py @@ -1,11 +1,31 @@ -import sys import logging import traceback import platform -from django.utils.timezone import now -from django.views.debug import get_exception_reporter_filter +from django.core import mail +from django.utils.log import AdminEmailHandler +from django.views.debug import ExceptionReporter, get_exception_reporter_filter +def format_record(record): + """ + Given a Django error LogRecord, format and return the interesting details, + for use by notification mechanisms like Humbug and e-mail. + """ + subject = '%s: %s' % (platform.node(), record.getMessage()) + + if record.exc_info: + stack_trace = ''.join(traceback.format_exception(*record.exc_info)) + else: + stack_trace = 'No stack trace available' + + try: + user = record.request.user + user_info = "%s (%s)" % (user.userprofile.full_name, user.email) + except Exception: + # Error was triggered by an anonymous user. + user_info = "Anonymous user (not logged in)" + + return (subject, stack_trace, user_info) class AdminHumbugHandler(logging.Handler): """An exception log handler that Humbugs log entries to the Humbug realm. @@ -24,7 +44,7 @@ class AdminHumbugHandler(logging.Handler): from zephyr.models import Recipient from zephyr.lib.actions import internal_send_message - subject = '%s: %s' % (platform.node(), record.getMessage()) + try: request = record.request @@ -40,16 +60,13 @@ class AdminHumbugHandler(logging.Handler): request_repr += "~~~~" except Exception: request_repr = "Request repr() unavailable." - subject = self.format_subject(subject) - if record.exc_info: - stack_trace = ''.join(traceback.format_exception(*record.exc_info)) - else: - stack_trace = 'No stack trace available' + subject, stack_trace, user_info = format_record(record) internal_send_message("humbug+errors@humbughq.com", - Recipient.STREAM, "devel", subject, - "~~~~ pytb\n%s\n\n~~~~\n%s" % (stack_trace, request_repr)) + Recipient.STREAM, "devel", self.format_subject(subject), + "Error generated by %s\n\n~~~~ pytb\n%s\n\n~~~~\n%s" % ( + user_info, stack_trace, request_repr)) def format_subject(self, subject): """ @@ -59,3 +76,26 @@ class AdminHumbugHandler(logging.Handler): formatted_subject = subject.replace('\n', '\\n').replace('\r', '\\r') return formatted_subject[:MAX_SUBJECT_LENGTH] +class HumbugAdminEmailHandler(AdminEmailHandler): + """An exception log handler that emails log entries to site admins. + + If the request is passed as the first argument to the log record, + request data will be provided in the email report. + """ + def emit(self, record): + try: + request = record.request + filter = get_exception_reporter_filter(request) + request_repr = filter.get_request_repr(request) + except Exception: + request = None + request_repr = "Request repr() unavailable." + + subject, stack_trace, user_info = format_record(record) + message = "Error generated by %s\n\n%s\n\n%s" % (user_info, stack_trace, + request_repr) + + reporter = ExceptionReporter(request, is_email=True, *record.exc_info) + html_message = self.include_html and reporter.get_traceback_html() or None + mail.mail_admins(self.format_subject(subject), message, fail_silently=True, + html_message=html_message)