96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
# Copyright The IETF Trust 2013-2020, All Rights Reserved
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from email.message import EmailMessage
|
|
from textwrap import dedent
|
|
from traceback import format_exception, extract_tb
|
|
|
|
from django.conf import settings
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from ietf.utils.mail import send_smtp
|
|
|
|
import debug # pyflakes:ignore
|
|
|
|
|
|
class EmailOnFailureCommand(BaseCommand):
|
|
"""Command that sends email when an exception occurs
|
|
|
|
Subclasses can override failure_message, failure_subject, and failure_recipients
|
|
to customize the behavior. Both failure_subject and failure_message are formatted
|
|
with keywords for interpolation. By default, the following substitutions are
|
|
available:
|
|
|
|
{error} - the exception instance
|
|
{error_summary} - multiline summary of error type and location where it occurred
|
|
|
|
More interpolation values can be added through the **extra argument to
|
|
make_failure_message().
|
|
|
|
By default, the full traceback will be attached to the notification email.
|
|
To disable this, set failure_email_includes_traceback to False.
|
|
|
|
When a command is executed, its handle() method will be called as usual.
|
|
If an exception occurs, instead of printing this to the terminal and
|
|
exiting with an error, a message generated via the make_failure_message()
|
|
method will be sent to failure_recipients. The command will exit successfully
|
|
to the shell.
|
|
|
|
This can be prevented for debugging by passing the --no-failure-email option.
|
|
In this case, the usual error handling will be used. To make this available,
|
|
the subclass must call super().add_arguments() in its own add_arguments() method.
|
|
"""
|
|
failure_message = dedent("""\
|
|
An exception occurred:
|
|
|
|
{error}
|
|
""")
|
|
failure_subject = 'Exception in management command'
|
|
failure_email_includes_traceback = True
|
|
|
|
@property
|
|
def failure_recipients(self):
|
|
return tuple(item[1] for item in settings.ADMINS)
|
|
|
|
def execute(self, *args, **options):
|
|
try:
|
|
super().execute(*args, **options)
|
|
except Exception as error:
|
|
if options['email_on_failure']:
|
|
msg = self.make_failure_message(error)
|
|
send_smtp(msg)
|
|
else:
|
|
raise
|
|
|
|
def _summarize_error(self, error):
|
|
frame = extract_tb(error.__traceback__)[-1]
|
|
return dedent(f"""\
|
|
Error details:
|
|
Exception type: {type(error).__module__}.{type(error).__name__}
|
|
File: {frame.filename}
|
|
Line: {frame.lineno}""")
|
|
|
|
def make_failure_message(self, error, **extra):
|
|
"""Generate an EmailMessage to report an error"""
|
|
format_values = dict(
|
|
error=error,
|
|
error_summary=self._summarize_error(error),
|
|
)
|
|
format_values.update(**extra)
|
|
msg = EmailMessage()
|
|
msg['To'] = self.failure_recipients
|
|
msg['From'] = settings.SERVER_EMAIL
|
|
msg['Subject'] = self.failure_subject.format(**format_values)
|
|
msg.set_content(
|
|
self.failure_message.format(**format_values)
|
|
)
|
|
if self.failure_email_includes_traceback:
|
|
msg.add_attachment(
|
|
''.join(format_exception(None, error, error.__traceback__)),
|
|
filename='traceback.txt',
|
|
)
|
|
return msg
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument('--no-failure-email', dest='email_on_failure', action='store_false',
|
|
help='Disable sending email on failure') |