mail.py 4.9 KB

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