diff --git a/agenda/error_mail.py b/agenda/error_mail.py new file mode 100644 index 0000000..882d9c5 --- /dev/null +++ b/agenda/error_mail.py @@ -0,0 +1,72 @@ +"""Send mail to admin when an error happens.""" + +import logging +from logging import Formatter +from logging.handlers import SMTPHandler + +import flask +from flask import g, request + +PROJECT = "agenda" + + +class MySMTPHandler(SMTPHandler): + """Custom SMTP handler to change mail subject.""" + + def getSubject(self, record: logging.LogRecord) -> str: + """Specify subject line for error mails.""" + subject = ( + f"{PROJECT} error: {record.exc_info[0].__name__}" + if (record.exc_info and record.exc_info[0]) + else f"{PROJECT} error: {record.pathname}:{record.lineno:d}" + ) + + if qid := getattr(g, "qid", None): + subject += f" {qid}" + + if label := getattr(g, "label", None): + subject += f": {label}" + + return subject + + +class RequestFormatter(Formatter): + """Custom logging formatter to include request.""" + + def format(self, record: logging.LogRecord) -> str: + """Record includes request.""" + record.request = request + return super().format(record) + + +def setup_error_mail(app: flask.Flask) -> None: + """Send mail to admins when an error happens.""" + formatter = RequestFormatter( + """ + Message type: {levelname} + Location: {pathname:s}:{lineno:d} + Module: {module:s} + Function: {funcName:s} + Time: {asctime:s} + GET args: {request.args!r} + URL: {request.url} + + Message: + + {message:s} + """, + style="{", + ) + + mail_handler = MySMTPHandler( + app.config["SMTP_HOST"], + app.config["MAIL_FROM"], + app.config["ADMINS"], + app.name + " error", + timeout=30, + ) + mail_handler.setFormatter(formatter) + + mail_handler.setLevel(logging.ERROR) + app.logger.propagate = True + app.logger.addHandler(mail_handler) diff --git a/web_view.py b/web_view.py index 65cfe0f..d412c93 100755 --- a/web_view.py +++ b/web_view.py @@ -15,12 +15,15 @@ import werkzeug.debug.tbtools import yaml import agenda.data +import agenda.error_mail import agenda.travel app = flask.Flask(__name__) app.debug = False app.config.from_object("config.default") +agenda.error_mail.setup_error_mail(app) + @app.errorhandler(werkzeug.exceptions.InternalServerError) def exception_handler(e: werkzeug.exceptions.InternalServerError) -> tuple[str, int]: