Отправка логов по SMTP с буферизацией и TLS-аутентификацией

Используя модуль logging для отправки сообщений журнала по электронной почте, можно столкнуться с проблемой, состоящей в том, что каждый раз, когда вы добавляете запись в журнал, по электронной почте отправляется отдельное письмо. Как отправлять письма только по достижении некоторого количества сообщений? И что делать, если почтовый сервис в обязательном порядке требует TLS-аутентификацию?

Проблему решает приведенный ниже класс — BufferingSMTPHandler. Он добавляет во внутренний буфер сообщения до тех пор, пока размер буфера не достигнет значения capacity. Затем все, что не было добавлено до текущего момента в буфер, отправляется одним письмом через SMTP на указанный в параметрах адрес эл. почты.

Решение создавалось в первую очередь для работы с SMTP-серверами, требующими TLS-аутентификацию, например smtp.gmail.com.

Код

class BufferingSMTPHandler(logging.handlers.BufferingHandler):
    def __init__(self, mailhost, fromaddr, toaddrs, subject, credentials, capacity):
        logging.handlers.BufferingHandler.__init__(self, capacity)
        self.mailhost, self.mailport = mailhost
        self.fromaddr = fromaddr
        self.toaddrs = toaddrs
        self.subject = subject
        self.username, self.password = credentials
        self.setFormatter(logging.Formatter("%(asctime)s %(levelname)-5s %(message)s"))

    def flush(self):
        if len(self.buffer) > 0:
            try:
                import smtplib
                from email.utils import formatdate
                port = self.mailport
                if not port:
                    port = smtplib.SMTP_PORT
                smtp = smtplib.SMTP(self.mailhost, port)
                msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % (
                    self.fromaddr, ",".join(self.toaddrs), self.subject
                )
                for record in self.buffer:
                    s = self.format(record)
                    msg = msg + s + "\r\n"
                if self.username:
                    smtp.ehlo()  # for tls add this line
                    smtp.starttls()  # for tls add this line
                    smtp.ehlo()  # for tls add this line
                    smtp.login(self.username, self.password)
                smtp.sendmail(self.fromaddr, self.toaddrs, msg)
                smtp.quit()
            except (KeyboardInterrupt, SystemExit):
                raise
            except Exception:
                self.handleError(None)  # no particular record
            self.buffer = []

Пример использования

Подготавливаем параметры:

params = dict(
    mailhost=("smtp.gmail.com", 587),
    fromaddr='your-email@gmail.com',
    toaddrs=['eu@zyatev.ru'],
    subject='Debian SSH Backup notification',
    credentials=('your-email@gmail.com', '<password>'),
    capacity=60
)

К экземпляру логгера добавляем обработчик BufferingSMTPHandler:

smtp_handler = BufferingSMTPHandler(**params)
smtp_handler.setLevel(logging.DEBUG)
logging.getLogger('').addHandler(smtp_handler)