How can I send an email using python logging's SMTPHandler and SSL How can I send an email using python logging's SMTPHandler and SSL flask flask

How can I send an email using python logging's SMTPHandler and SSL


EDIT - see bottom of post for more up-to-date, code

Figured it out with thanks to Ned Deily pointing out that smtplib (which sits under SMTPHandler) requires special treatment. I also found this post demonstrating how to do that, by overloading the SMTPHandler (in that case to fix a TLS problem).

Using smtplib.SMTP_SSL (see smtplib docs), rather than the straightforward smtplib.SMTP, I was able to get the whole system working. This is the utils/logs.py file I use to set up the handlers (which should be a nice example of file, as well as email, handlers):

from your.application.file import appimport smtplibimport loggingfrom logging.handlers import RotatingFileHandler, SMTPHandler# Provide a class to allow SSL (Not TLS) connection for mail handlers by overloading the emit() methodclass 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 logsfile_handler = RotatingFileHandler('logs/app.log', maxBytes=1*1024*1024, backupCount=100)# Apply format to the log messagesformatter = 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 notif app.debug:    file_handler.setLevel(logging.DEBUG)else:    file_handler.setLevel(logging.WARN)# Create equivalent mail handlermail_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 formatmail_handler.setFormatter(logging.Formatter('''Message type:       %(levelname)sLocation:           %(pathname)s:%(lineno)dModule:             %(module)sFunction:           %(funcName)sTime:               %(asctime)sMessage:%(message)s'''))# Only email errors, not warningsmail_handler.setLevel(logging.ERROR)

This is registered in my application file with:

# 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 loggingfrom .utils.logs import mail_handler, file_handlerloggers = [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)

EDIT:

Commenter @EduGord has had trouble emitting the record correctly. Digging deeper, the base SMTPHandler class is sending messages differently than it was 3+ years ago.

This updated emit() method should get the message to format correctly:

from email.message import EmailMessageimport email.utilsclass 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)

Hope this helps somebody!