【问题标题】:How can I send an email using python logging's SMTPHandler and SSL如何使用 python 日志记录的 SMTPHandler 和 SSL 发送电子邮件
【发布时间】:2019-11-11 14:51:13
【问题描述】:

我正在开发一个烧瓶应用程序,我想在其中将错误级别日志记录发送到电子邮件地址。我尝试设置典型的错误处理程序:

mail_handler = SMTPHandler(mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']),
                           fromaddr=app.config['MAIL_FROM_EMAIL'],
                           toaddrs=['me@my_address.com'],
                           subject='The server died. That sucks... :(',
                           credentials=(app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD']))

请注意,配置值是使用 flask-mail 设置的,MAIL_USE_SSL=TrueMAIL_PORT=465

但是,在调用错误时(在测试期间故意)我收到套接字超时错误 - 除了端口之外,我看不到如何告诉处理程序使用 SSL。可以传递一个 secure=() 参数(请参阅 the SMTPHandler docs),但它指定我们使用 TLS,而不是 SSL。

任何线索如何做到这一点?谢谢!

【问题讨论】:

    标签: python ssl flask error-logging smtplib


    【解决方案1】:

    编辑 - 查看帖子底部了解更多最新代码

    感谢Ned Deily 指出 smtplib(位于 SMTPHandler 下)需要特殊处理。我还发现this post 演示了如何通过重载 SMTPHandler(在这种情况下修复 TLS 问题)来做到这一点。

    使用smtplib.SMTP_SSL (see smtplib docs),而不是简单的smtplib.SMTP,我能够让整个系统正常工作。这是我用来设置处理程序的 utils/logs.py 文件(这应该是一个很好的文件示例,以及电子邮件、处理程序):

    from your.application.file import app
    
    import smtplib
    import logging
    from logging.handlers import RotatingFileHandler, SMTPHandler
    
    
    # Provide a class to allow SSL (Not TLS) connection for mail handlers by overloading the emit() method
    class SSLSMTPHandler(SMTPHandler):
        def emit(self, record):
            """
            Emit a record.
            """
            try:
                port = self.mailport
                if not port:
                    port = smtplib.SMTP_PORT
                smtp = smtplib.SMTP_SSL(self.mailhost, port)
                msg = self.format(record)
                if self.username:
                    smtp.login(self.username, self.password)
                smtp.sendmail(self.fromaddr, self.toaddrs, msg)
                smtp.quit()
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                self.handleError(record)
    
    
    # Create file handler for error/warning/info/debug logs
    file_handler = RotatingFileHandler('logs/app.log', maxBytes=1*1024*1024, backupCount=100)
    
    # Apply format to the log messages
    formatter = logging.Formatter("[%(asctime)s] |  %(levelname)s | {%(pathname)s:%(lineno)d} | %(message)s")
    file_handler.setFormatter(formatter)
    
    # Set the level according to whether we're debugging or not
    if app.debug:
        file_handler.setLevel(logging.DEBUG)
    else:
        file_handler.setLevel(logging.WARN)
    
    # Create equivalent mail handler
    mail_handler = SSLSMTPHandler(mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']),
                               fromaddr=app.config['MAIL_FROM_EMAIL'],
                               toaddrs='my@emailaddress.com',
                               subject='Your app died. Sad times...',
                               credentials=(app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD']))
    
    # Set the email format
    mail_handler.setFormatter(logging.Formatter('''
    Message type:       %(levelname)s
    Location:           %(pathname)s:%(lineno)d
    Module:             %(module)s
    Function:           %(funcName)s
    Time:               %(asctime)s
    
    Message:
    
    %(message)s
    '''))
    
    # Only email errors, not warnings
    mail_handler.setLevel(logging.ERROR)
    

    这是在我的应用程序文件中注册的:

    # Register the handlers against all the loggers we have in play
    # This is done after app configuration and SQLAlchemy initialisation, 
    # drop the sqlalchemy if not using - I thought a full example would be helpful.
    import logging
    from .utils.logs import mail_handler, file_handler
    loggers = [app.logger, logging.getLogger('sqlalchemy'), logging.getLogger('werkzeug')]
    for logger in loggers:
        logger.addHandler(file_handler)
        # Note - I added a boolean configuration parameter, MAIL_ON_ERROR, 
        # to allow direct control over whether to email on errors. 
        # You may wish to use 'if not app.debug' instead.
        if app.config['MAIL_ON_ERROR']:
            logger.addHandler(mail_handler)
    

    编辑:

    评论者@EduGord 无法正确发出记录。 深入挖掘,基础 SMTPHandler 类发送消息的方式与 3 多年前不同。

    这个更新的emit() 方法应该可以正确格式化消息:

    from email.message import EmailMessage
    import email.utils
    class SSLSMTPHandler(SMTPHandler):
    
        def emit(self, record):
            """
            Emit a record.
            """
            try:
                port = self.mailport
                if not port:
                    port = smtplib.SMTP_PORT
                smtp = smtplib.SMTP_SSL(self.mailhost, port)
                msg = EmailMessage()
                msg['From'] = self.fromaddr
                msg['To'] = ','.join(self.toaddrs)
                msg['Subject'] = self.getSubject(record)
                msg['Date'] = email.utils.localtime()
                msg.set_content(self.format(record))
                if self.username:
                    smtp.login(self.username, self.password)
                smtp.send_message(msg, self.fromaddr, self.toaddrs)
                smtp.quit()
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                self.handleError(record)
    

    希望这对某人有所帮助!

    【讨论】:

    • 感谢您的回答,但电子邮件没有收到主题。但是,当用 msg = 'Subject: {}\n\n{}'.format(self.subject, self.format(record)) 替换 SSLSMTPHandler.emit 中的 msg 时,它工作得很好
    • 对于我的 2c;在 SMTPHandler 中格式化记录违反了关注点分离……记录在到达 emit() 时应该已经正确格式化。您确定您正确传递了主题关键字吗?
    • 我是@thclark:SSLSMTPHandler([...]subject='Falha em meuApp',[...]) 。另外,我还必须修改 smtp.sendmail(self.fromaddr, self.toaddrs, msg.encode("iso-8859-1")) 以允许来自我的母语 (PT-BR) 的特殊字符。
    • @EduGord 你可以试试我发布的解决方案,看看它是否有效?
    • 我已经测试了您的解决方案。好像你忘了声明味精。我设置了 msg = EmailMessage() 并且它现在可以正常工作。再次感谢您!
    猜你喜欢
    • 1970-01-01
    • 2016-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    相关资源
    最近更新 更多