【问题标题】:How to make SMTPHandler not block如何使 SMTPHandler 不阻塞
【发布时间】:2012-01-26 20:19:15
【问题描述】:

我安装了本地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 脚本。怎么会?如何让它不阻塞脚本?

【问题讨论】:

    标签: python logging smtp handler


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 例如调用可能与您的 MTA 一起提供的本地 sendmail 程序,并在不使用 SMTP 的情况下提交到本地 SMTP 服务器。
        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          作为 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 两次,可能不需要。

          【讨论】:

            猜你喜欢
            • 2013-11-16
            • 2014-05-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-07
            • 1970-01-01
            相关资源
            最近更新 更多