mail.py 5.3 KB

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