2013-04-23 18:51:17 +02:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
2013-10-31 18:33:19 +01:00
|
|
|
from django.conf import settings
|
|
|
|
|
2012-12-06 22:00:34 +01:00
|
|
|
import logging
|
|
|
|
import traceback
|
2013-01-02 23:25:40 +01:00
|
|
|
import platform
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2013-01-16 20:45:07 +01:00
|
|
|
from django.core import mail
|
|
|
|
from django.utils.log import AdminEmailHandler
|
|
|
|
from django.views.debug import ExceptionReporter, get_exception_reporter_filter
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2013-01-16 20:45:07 +01:00
|
|
|
def format_record(record):
|
|
|
|
"""
|
|
|
|
Given a Django error LogRecord, format and return the interesting details,
|
2013-08-06 21:35:33 +02:00
|
|
|
for use by notification mechanisms like Zulip and e-mail.
|
2013-01-16 20:45:07 +01:00
|
|
|
"""
|
|
|
|
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:
|
2013-03-29 17:39:53 +01:00
|
|
|
user_profile = record.request.user
|
|
|
|
user_info = "%s (%s)" % (user_profile.full_name, user_profile.email)
|
2013-01-16 20:45:07 +01:00
|
|
|
except Exception:
|
|
|
|
# Error was triggered by an anonymous user.
|
|
|
|
user_info = "Anonymous user (not logged in)"
|
|
|
|
|
|
|
|
return (subject, stack_trace, user_info)
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2013-08-06 21:35:33 +02:00
|
|
|
class AdminZulipHandler(logging.Handler):
|
|
|
|
"""An exception log handler that Zulips log entries to the Zulip realm.
|
2012-12-06 22:00:34 +01:00
|
|
|
|
|
|
|
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):
|
2013-10-31 18:33:19 +01:00
|
|
|
if not settings.ERROR_BOT:
|
|
|
|
return
|
|
|
|
|
2012-12-06 22:00:34 +01:00
|
|
|
# We have to defer imports to avoid circular imports in settings.py.
|
2013-07-29 23:03:31 +02:00
|
|
|
from zerver.lib.actions import internal_send_message
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2013-01-16 20:45:07 +01:00
|
|
|
|
2012-12-06 22:00:34 +01:00
|
|
|
try:
|
|
|
|
request = record.request
|
2012-12-19 08:35:10 +01:00
|
|
|
|
|
|
|
filter = get_exception_reporter_filter(request)
|
2012-12-19 08:36:30 +01:00
|
|
|
request_repr = "Request info:\n~~~~\n"
|
2012-12-11 21:19:06 +01:00
|
|
|
request_repr += "- path: %s\n" % (request.path,)
|
|
|
|
if request.method == "GET":
|
|
|
|
request_repr += "- GET: %s\n" % (request.GET,)
|
|
|
|
elif request.method == "POST":
|
2012-12-19 08:35:10 +01:00
|
|
|
request_repr += "- POST: %s\n" % (filter.get_post_parameters(request),)
|
2013-01-02 23:26:32 +01:00
|
|
|
for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
|
2012-12-19 08:36:30 +01:00
|
|
|
request_repr += "- %s: \"%s\"\n" % (field, request.META.get(field, "(None)"))
|
|
|
|
request_repr += "~~~~"
|
2012-12-06 22:00:34 +01:00
|
|
|
except Exception:
|
2013-03-15 17:00:28 +01:00
|
|
|
request_repr = "Log record message:\n%s" % (record.getMessage(),)
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2013-01-16 20:45:07 +01:00
|
|
|
subject, stack_trace, user_info = format_record(record)
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2013-01-25 20:15:26 +01:00
|
|
|
try:
|
2013-10-31 18:33:19 +01:00
|
|
|
internal_send_message(settings.ERROR_BOT,
|
2013-05-10 20:30:40 +02:00
|
|
|
"stream", "errors", self.format_subject(subject),
|
2013-01-25 20:15:26 +01:00
|
|
|
"Error generated by %s\n\n~~~~ pytb\n%s\n\n~~~~\n%s" % (
|
|
|
|
user_info, stack_trace, request_repr))
|
|
|
|
except:
|
|
|
|
# If this breaks, complain loudly but don't pass the traceback up the stream
|
|
|
|
# However, we *don't* want to use logging.exception since that could trigger a loop.
|
|
|
|
logging.warning("Reporting an exception triggered an exception!", exc_info=True)
|
2012-12-06 22:00:34 +01:00
|
|
|
|
|
|
|
def format_subject(self, subject):
|
|
|
|
"""
|
2012-12-07 01:05:14 +01:00
|
|
|
Escape CR and LF characters, and limit length to MAX_SUBJECT_LENGTH.
|
2012-12-06 22:00:34 +01:00
|
|
|
"""
|
2013-07-29 23:03:31 +02:00
|
|
|
from zerver.models import MAX_SUBJECT_LENGTH
|
2012-12-06 22:00:34 +01:00
|
|
|
formatted_subject = subject.replace('\n', '\\n').replace('\r', '\\r')
|
2012-12-07 01:05:14 +01:00
|
|
|
return formatted_subject[:MAX_SUBJECT_LENGTH]
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2013-08-06 21:35:33 +02:00
|
|
|
class ZulipAdminEmailHandler(AdminEmailHandler):
|
2013-01-16 20:45:07 +01:00
|
|
|
"""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
|
2013-03-15 17:00:28 +01:00
|
|
|
request_repr = "Log record message:\n%s" % (record.getMessage(),)
|
2013-01-16 20:45:07 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2013-03-15 17:01:30 +01:00
|
|
|
try:
|
|
|
|
reporter = ExceptionReporter(request, is_email=True, *record.exc_info)
|
|
|
|
html_message = self.include_html and reporter.get_traceback_html() or None
|
|
|
|
except Exception:
|
|
|
|
html_message = None
|
2013-01-16 20:45:07 +01:00
|
|
|
mail.mail_admins(self.format_subject(subject), message, fail_silently=True,
|
|
|
|
html_message=html_message)
|