mail.py 4.6 KB

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