如何使SMTPHandler不阻塞

9 投票
7 回答
4022 浏览
提问于 2025-04-17 08:54

我安装了一个本地的SMTP服务器,然后用logging.handlers.SMTPHandler这个工具来记录一个异常,使用了以下代码:

import logging
import logging.handlers
import time
gm = logging.handlers.SMTPHandler(("localhost", 25), 'info@somewhere.com', ['my_email@gmail.com'], 'Hello Exception!',)
gm.setLevel(logging.ERROR)
logger.addHandler(gm)
t0 = time.clock()
try:
    1/0
except:
    logger.exception('testest')
print time.clock()-t0

这个过程花了超过1秒钟才完成,这段时间里我的Python脚本都被阻塞了。为什么会这样呢?我该怎么做才能让它不阻塞脚本呢?

7 个回答

4

对我来说,最简单的异步SMTP处理方法就是重写一下emit这个方法,然后在一个新线程里使用原来的方法。在这种情况下,GIL(全局解释器锁)不是问题,因为我们会有一个对SMTP服务器的输入输出操作,这个操作会释放GIL。下面是代码示例:

class ThreadedSMTPHandler(SMTPHandler):
    def emit(self, record):
        thread = Thread(target=SMTPHandler.emit, args=(self, record))
        thread.start()
7

你可以使用 QueueHandlerQueueListener。下面是文档中的一段话:

QueueListener 类和 QueueHandler 可以一起使用,让处理日志的工作在一个单独的线程中进行,而不是在记录日志的线程中。这在网页应用和其他服务应用中非常重要,因为处理客户请求的线程需要尽可能快地响应,而一些可能比较慢的操作(比如通过 SMTPHandler 发送邮件)则可以在另一个线程中完成。

可惜的是,这些功能只在 Python 3.2 及以后的版本中可用。

13

这是我正在使用的实现方式,我是根据这个适配Gmail的SMTPHandler来做的。
我把发送到SMTP的部分放在了一个单独的线程里。

import logging.handlers
import smtplib
from threading import Thread

def smtp_at_your_own_leasure(mailhost, port, username, password, fromaddr, toaddrs, msg):
    smtp = smtplib.SMTP(mailhost, port)
    if username:
        smtp.ehlo() # for tls add this line
        smtp.starttls() # for tls add this line
        smtp.ehlo() # for tls add this line
        smtp.login(username, password)
    smtp.sendmail(fromaddr, toaddrs, msg)
    smtp.quit()

class ThreadedTlsSMTPHandler(logging.handlers.SMTPHandler):
    def emit(self, record):
        try:
            import string # for tls add this line
            try:
                from email.utils import formatdate
            except ImportError:
                formatdate = self.date_time
            port = self.mailport
            if not port:
                port = smtplib.SMTP_PORT
            msg = self.format(record)
            msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (
                            self.fromaddr,
                            string.join(self.toaddrs, ","),
                            self.getSubject(record),
                            formatdate(), msg)
            thread = Thread(target=smtp_at_your_own_leasure, args=(self.mailhost, port, self.username, self.password, self.fromaddr, self.toaddrs, msg))
            thread.start()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

使用示例:

logger = logging.getLogger()

gm = ThreadedTlsSMTPHandler(("smtp.gmail.com", 587), 'bugs@my_company.com', ['admin@my_company.com'], 'Error found!', ('my_company_account@gmail.com', 'top_secret_gmail_password'))
gm.setLevel(logging.ERROR)

logger.addHandler(gm)

try:
    1/0
except:
    logger.exception('FFFFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUUUU-')

撰写回答