Skip to content

Instantly share code, notes, and snippets.

@doekman
Last active September 28, 2021 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save doekman/d24e233035c0a193d4890eaf9703e220 to your computer and use it in GitHub Desktop.
Save doekman/d24e233035c0a193d4890eaf9703e220 to your computer and use it in GitHub Desktop.
A replacement for `logging.handlers.SMTPHandler` that integrates with Flask-Mail
import logging
from enum import Enum
def _has_newline(line):
"""Used by has_bad_header to check for \\r or \\n"""
if line and ('\r' in line or '\n' in line):
return True
return False
def _is_bad_subject(subject):
'''Copied from: flask_mail.py class Message def has_bad_headers'''
if _has_newline(subject):
for linenum, line in enumerate(subject.split('\r\n')):
if not line:
return True
if linenum > 0 and line[0] not in '\t ':
return True
if _has_newline(line):
return True
if len(line.strip()) == 0:
return True
return False
class FlaskMailSubjectFormatter(logging.Formatter):
def format(self, record):
record.message = record.getMessage()
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
s = self.formatMessage(record)
return s
class FlaskMailTextFormatter(logging.Formatter):
pass
# TODO: hier nog niet tevreden over (vooral logger.error(..., exc_info, stack_info))
class FlaskMailHTMLFormatter(logging.Formatter):
pre_template = '<h1>%s</h1><pre>%s</pre>'
def formatException(self, exc_info):
formatted_exception = logging.Handler.formatException(self, exc_info)
return FlaskMailHTMLFormatter.pre_template % ('Exception information', formatted_exception)
def formatStack(self, stack_info):
return FlaskMailHTMLFormatter.pre_template % ('<h1>Stack information</h1><pre>%s</pre>', stack_info)
# see: https://github.com/python/cpython/blob/3.6/Lib/logging/__init__.py (class Handler)
class FlaskMailHandler(logging.Handler):
def __init__(self, mailer, subject_template, level=logging.NOTSET):
logging.Handler.__init__(self, level)
self.mailer = mailer
self.send_to = mailer.app.config['MAIL_UTILS_ERROR_SEND_TO']
self.subject_template = subject_template
self.html_formatter = None
def setFormatter(self, text_fmt, html_fmt=None):
"""
Set the formatters for this handler. Provide at least one formatter.
When no text_fmt is provided, no text-part is created for the email body.
"""
assert (text_fmt, html_fmt) != (None, None), 'At least one formatter should be provided'
if type(text_fmt)==str:
text_fmt = FlaskMailTextFormatter(text_fmt)
self.formatter = text_fmt
if type(html_fmt)==str:
html_fmt = FlaskMailHTMLFormatter(html_fmt)
self.html_formatter = html_fmt
def getSubject(self, record):
fmt = FlaskMailSubjectFormatter(self.subject_template)
subject = fmt.format(record)
#Since templates can cause header problems, and we rather have a incomplete email then an error, we fix this
if _is_bad_subject(subject):
subject='FlaskMailHandler log-entry from %s [original subject is replaced, because it would result in a bad header]' % self.mailer.app.name
return subject
def emit(self, record):
try:
from flask_mail import Message
msg = Message(self.getSubject(record), recipients=self.send_to)
if self.formatter:
msg.body = self.format(record)
if self.html_formatter:
msg.html = self.html_formatter.format(record)
self.mailer.send(msg)
except Exception:
self.handleError(record)
def register_mail_error_handler(mailer):
subject_template = 'Web-app problems in %(module)s > %(funcName)s'
text_template = '''
Message type: %(levelname)s
Location: %(pathname)s:%(lineno)d
Module: %(module)s
Function: %(funcName)s
Time: %(asctime)s
Message:
%(message)s'''
html_template = '''
<style>th { text-align: right}</style><table>
<tr><th>Message type:</th><td>%(levelname)s</td></tr>
<tr> <th>Location:</th><td>%(pathname)s:%(lineno)d</td></tr>
<tr> <th>Module:</th><td>%(module)s</td></tr>
<tr> <th>Function:</th><td>%(funcName)s</td></tr>
<tr> <th>Time:</th><td>%(asctime)s</td></tr>
</table>
<h2>Message</h2>
<pre>%(message)s</pre>'''
import logging
from .utils.flask_mail import FlaskMailHandler
mail_handler = FlaskMailHandler(mailer, subject_template)
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(text_template, html_template)
app.logger.addHandler(mail_handler)
if not app.debug:
register_mail_error_handler(mail)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment