mail.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.utils.mail
  4. ~~~~~~~~~~~~~~~~~
  5. How task error emails are formatted and sent.
  6. """
  7. from __future__ import absolute_import
  8. import smtplib
  9. import traceback
  10. import warnings
  11. from email.mime.text import MIMEText
  12. from .functional import maybe_list
  13. from .imports import symbol_by_name
  14. class SendmailWarning(UserWarning):
  15. """Problem happened while sending the email message."""
  16. class Message(object):
  17. def __init__(self, to=None, sender=None, subject=None, body=None,
  18. charset='us-ascii'):
  19. self.to = maybe_list(to)
  20. self.sender = sender
  21. self.subject = subject
  22. self.body = body
  23. self.charset = charset
  24. def __repr__(self):
  25. return '<Email: To:{0.to!r} Subject:{0.subject!r}>'.format(self)
  26. def __str__(self):
  27. msg = MIMEText(self.body, 'plain', self.charset)
  28. msg['Subject'] = self.subject
  29. msg['From'] = self.sender
  30. msg['To'] = ', '.join(self.to)
  31. return msg.as_string()
  32. class Mailer(object):
  33. def __init__(self, host='localhost', port=0, user=None, password=None,
  34. timeout=2, use_ssl=False, use_tls=False):
  35. self.host = host
  36. self.port = port
  37. self.user = user
  38. self.password = password
  39. self.timeout = timeout
  40. self.use_ssl = use_ssl
  41. self.use_tls = use_tls
  42. def send(self, message, fail_silently=False, **kwargs):
  43. try:
  44. self._send(message, **kwargs)
  45. except Exception as exc:
  46. if not fail_silently:
  47. raise
  48. warnings.warn(SendmailWarning(
  49. 'Mail could not be sent: {0!r} {1!r}\n{2!r}'.format(
  50. exc, {'To': ', '.join(message.to),
  51. 'Subject': message.subject},
  52. traceback.format_stack())))
  53. def _send(self, message, **kwargs):
  54. Client = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP
  55. client = Client(self.host, self.port, timeout=self.timeout, **kwargs)
  56. if self.use_tls:
  57. client.ehlo()
  58. client.starttls()
  59. client.ehlo()
  60. if self.user and self.password:
  61. client.login(self.user, self.password)
  62. client.sendmail(message.sender, message.to, str(message))
  63. client.quit()
  64. class ErrorMail(object):
  65. """Defines how and when task error e-mails should be sent.
  66. :param task: The task instance that raised the error.
  67. :attr:`subject` and :attr:`body` are format strings which
  68. are passed a context containing the following keys:
  69. * name
  70. Name of the task.
  71. * id
  72. UUID of the task.
  73. * exc
  74. String representation of the exception.
  75. * args
  76. Positional arguments.
  77. * kwargs
  78. Keyword arguments.
  79. * traceback
  80. String representation of the traceback.
  81. * hostname
  82. Worker hostname.
  83. """
  84. # pep8.py borks on a inline signature separator and
  85. # says "trailing whitespace" ;)
  86. EMAIL_SIGNATURE_SEP = '-- '
  87. #: Format string used to generate error email subjects.
  88. subject = """\
  89. [celery@{hostname}] Error: Task {name} ({id}): {exc!r}
  90. """
  91. #: Format string used to generate error email content.
  92. body = """
  93. Task {{name}} with id {{id}} raised exception:\n{{exc!r}}
  94. Task was called with args: {{args}} kwargs: {{kwargs}}.
  95. The contents of the full traceback was:
  96. {{traceback}}
  97. {EMAIL_SIGNATURE_SEP}
  98. Just to let you know,
  99. py-celery at {{hostname}}.
  100. """.format(EMAIL_SIGNATURE_SEP=EMAIL_SIGNATURE_SEP)
  101. error_whitelist = None
  102. def __init__(self, task, **kwargs):
  103. self.task = task
  104. self.email_subject = kwargs.get('subject', self.subject)
  105. self.email_body = kwargs.get('body', self.body)
  106. self.error_whitelist = getattr(task, 'error_whitelist')
  107. def should_send(self, context, exc):
  108. """Returns true or false depending on if a task error mail
  109. should be sent for this type of error."""
  110. allow_classes = tuple(map(symbol_by_name, self.error_whitelist))
  111. return not self.error_whitelist or isinstance(exc, allow_classes)
  112. def format_subject(self, context):
  113. return self.subject.strip().format(**context)
  114. def format_body(self, context):
  115. return self.body.strip().format(**context)
  116. def send(self, context, exc, fail_silently=True):
  117. if self.should_send(context, exc):
  118. self.task.app.mail_admins(self.format_subject(context),
  119. self.format_body(context),
  120. fail_silently=fail_silently)