]> git.lizzy.rs Git - cheatdb.git/commitdiff
Add email on Flask error
authorrubenwardy <rw@rubenwardy.com>
Fri, 6 Jul 2018 21:52:19 +0000 (22:52 +0100)
committerrubenwardy <rw@rubenwardy.com>
Fri, 6 Jul 2018 21:55:55 +0000 (22:55 +0100)
app/__init__.py
app/maillogger.py [new file with mode: 0644]
app/tasks/emails.py
config.example.cfg

index 8b7dd6a4e0bd5e24ef7c55c8ac9fc86af168228c..37c6cc8a98fd1321094ab1a0af4ab0f4bbc5865e 100644 (file)
@@ -37,5 +37,9 @@ csrf = CsrfProtect(app)
 mail = Mail(app)
 pages = FlatPages(app)
 
+if not app.debug:
+       from .maillogger import register_mail_error_handler
+       register_mail_error_handler(app, mail)
+
 from . import models, tasks
 from .views import *
diff --git a/app/maillogger.py b/app/maillogger.py
new file mode 100644 (file)
index 0000000..585cb29
--- /dev/null
@@ -0,0 +1,109 @@
+import logging
+from enum import Enum
+from app.tasks.emails import sendEmailRaw
+
+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):
+               text = self.format(record)                              if self.formatter         else None
+               html = self.html_formatter.format(record) if self.html_formatter else None
+               sendEmailRaw.delay(self.send_to, self.getSubject(record), text, html)
+
+
+def register_mail_error_handler(app, mailer):
+       subject_template = "ContentDB crashed (%(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
+       mail_handler = FlaskMailHandler(mailer, subject_template)
+       mail_handler.setLevel(logging.ERROR)
+       mail_handler.setFormatter(text_template, html_template)
+       app.logger.addHandler(mail_handler)
index fbefbc76e0b4251aa1977e7410be59b3dbac5c26..5af57698cc97b3213ce85b81683c2edaeda310f0 100644 (file)
@@ -22,7 +22,17 @@ from app.tasks import celery
 
 @celery.task()
 def sendVerifyEmail(newEmail, token):
-    msg = Message("Verify email address", recipients=[newEmail])
-    msg.body = "This is a verification email!"
-    msg.html = render_template("emails/verify.html", token=token)
-    mail.send(msg)
+       msg = Message("Verify email address", recipients=[newEmail])
+       msg.body = "This is a verification email!"
+       msg.html = render_template("emails/verify.html", token=token)
+       mail.send(msg)
+
+@celery.task()
+def sendEmailRaw(to, subject, text, html):
+       from flask_mail import Message
+       msg = Message(subject, recipients=to)
+       if text:
+               msg.body = text
+       if html:
+               msg.html = html
+       mail.send(msg)
index 41d685c2cd7ed09ec29b114c131c933cbb32c087..ae78f29bee08ea92a31aa09018843c71d57a4918 100644 (file)
@@ -22,3 +22,4 @@ MAIL_DEFAULT_SENDER=""
 MAIL_SERVER=""
 MAIL_PORT=587
 MAIL_USE_TLS=True
+MAIL_UTILS_ERROR_SEND_TO=[""]