如何使SMTPHandler不阻止

时间:2011-12-23 13:25:16

标签: python logging smtp handler

我安装了一个本地SMTP server并使用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脚本。怎么会?如何让它不阻止脚本?

6 个答案:

答案 0 :(得分:13)

这是我正在使用的实现,我基于this Gmail adapted 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-')

答案 1 :(得分:5)

您可以使用QueueHandlerQueueListener。取自文档:

  

与QueueListener类一起,可以使用QueueHandler来释放   处理程序在一个单独的线程上完成它们的工作   伐木。这在Web应用程序和其他应用程序中很重要   服务应用程序,服务客户端的线程需要响应   尽可能快地进行任何潜在的慢速操作(例如   通过SMTPHandler发送电子邮件)是在一个单独的线程上完成的。

唉,他们只能从Python 3.2开始使用。

答案 2 :(得分:4)

对我来说,最简单的异步smtp处理程序形式只是覆盖emit方法并在新线程中使用原始方法。在这种情况下,GIL不是问题,因为对SMTP服务器的I / O调用会释放GIL。代码如下

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

答案 3 :(得分:0)

很可能你需要编写自己的日志处理程序,以便在后台发送电子邮件。

答案 4 :(得分:0)

在Python中编码时要记住的是GIL(全局解释器锁)。此锁定可防止同时发生多个进程。在Python中有许多“阻塞”活动。他们将阻止一切,直到他们完成。

目前围绕GIL的唯一方法是推迟尝试外部源的操作,如aix和MattH建议,或使用多处理模块实现代码(http://docs.python.org /library/multiprocessing.html)以便一个进程处理消息的发送,其余进程由另一个进程处理。

答案 5 :(得分:0)

由于OP pointed outQueueHandlerQueueListener可以解决问题!我进行了一些研究,并改编了this page上的代码,为您提供了一些示例代码:

# In your init part,
# assuming your logger is given by the "logger" variable
# and your config is storded in the "config" dictionary

logging_queue = Queue(-1)
queue_handler = QueueHandler(logging_queue)
queue_handler.setLevel(logging.ERROR)
queue_handler.setFormatter(logging_formatter)
logger.addHandler(queue_handler)

smtp_handler = SMTPHandler(mailhost=(config['MAIL_SERVER'], config['MAIL_PORT']),
                           fromaddr=config['MAIL_SENDER'],
                           toaddrs=[config['ERROR_MAIL']],
                           subject='Application error',
                           credentials=(config['MAIL_USERNAME'], config['MAIL_PASSWORD']),
                           secure=tuple())
smtp_handler.setLevel(logging.ERROR)
smtp_handler.setFormatter(logging_formatter)

queue_listener = QueueListener(logging_queue, smtp_handler)
queue_listener.start()

# Let's test it. The warning is not mailed, the error is.
logger.warning('Test warning')
logger.error('Test error')

我不确定的是是否有必要两次使用setLevelsetFormatter,也许没有必要。