From 45eec97bf34b6093f77bf6cac597cddcbd5720ba Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 4 Dec 2014 15:56:20 -0800 Subject: [PATCH] Add exception e-mail handler --- rophako/app.py | 11 +++++++ rophako/jsondb.py | 6 +++- rophako/utils.py | 59 ++++++++++++++++++++++++++++++++++++- rophako/www/errors/500.html | 10 +++++++ runserver.py | 7 ++++- 5 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 rophako/www/errors/500.html diff --git a/rophako/app.py b/rophako/app.py index 6bef520..597c281 100644 --- a/rophako/app.py +++ b/rophako/app.py @@ -29,6 +29,7 @@ Config.load_plugins() from rophako import __version__ from rophako.plugin import load_plugin +from rophako.log import logger import rophako.model.tracking as Tracking import rophako.utils @@ -156,6 +157,16 @@ def index(): return catchall("index") +@app.errorhandler(Exception) +def catch_exception(error): + """Catch unexpected Python exceptions and e-mail them out.""" + logger.error("INTERNAL SERVER ERROR: {}".format(str(error))) + + # E-mail it out. + rophako.utils.handle_exception(error) + return rophako.utils.template("errors/500.html") + + @app.errorhandler(404) def not_found(error): return render_template('errors/404.html', **g.info), 404 diff --git a/rophako/jsondb.py b/rophako/jsondb.py index 3650f43..26c296a 100644 --- a/rophako/jsondb.py +++ b/rophako/jsondb.py @@ -13,6 +13,7 @@ import json import time from rophako.settings import Config +from rophako.utils import handle_exception from rophako.log import logger redis_client = None @@ -137,7 +138,10 @@ def read_json(path): data = json.loads(text) except: logger.error("Couldn't decode JSON data from {}".format(path)) - logger.error(text) + handle_exception(Exception("Couldn't decode JSON from {}\n{}".format( + path, + text, + ))) data = None return data diff --git a/rophako/utils.py b/rophako/utils.py index e74a808..82b0472 100644 --- a/rophako/utils.py +++ b/rophako/utils.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from flask import g, session, request, render_template, flash, redirect, url_for +from flask import (g, session, request, render_template, flash, redirect, + url_for, current_app) from functools import wraps import codecs import uuid @@ -12,6 +13,7 @@ import smtplib import markdown import json import urlparse +import traceback from rophako.log import logger from rophako.settings import Config @@ -192,6 +194,61 @@ def send_email(to, subject, message, sender=None, reply_to=None): server.quit() +def handle_exception(error): + """Send an e-mail to the site admin when an exception occurs.""" + if current_app.config.get("DEBUG"): + print traceback.format_exc() + raise + + import rophako.jsondb as JsonDB + + # Don't spam too many e-mails in a short time frame. + cache = JsonDB.get_cache("exception_catcher") + if cache: + last_exception = int(cache) + if int(time.time()) - last_exception < 120: + # Only one e-mail per 2 minutes, minimum + logger.error("RAPID EXCEPTIONS, DROPPING") + return + JsonDB.set_cache("exception_catcher", int(time.time())) + + username = "anonymous" + try: + if "username" in g.info["session"]: + username = g.info["session"]["username"] + except: + pass + + # Get the timestamp. + timestamp = time.ctime(time.time()) + + # Exception's traceback. + error = str(error.__class__.__name__) + ": " + str(error) + stacktrace = error + "\n\n" \ + + "==== Start Traceback ====\n" \ + + traceback.format_exc() \ + + "==== End Traceback ====\n" + + # Construct the subject and message + subject = "Internal Server Error on {} - {} - {}".format( + Config.site.site_name, + username, + timestamp, + ) + message = "{} has experienced an exception on the route: {}".format( + username, + request.path, + ) + message += "\n\n" + stacktrace + + # Send the e-mail. + send_email( + to=Config.site.notify_address, + subject=subject, + message=message, + ) + + def generate_csrf_token(): """Generator for CSRF tokens.""" if "_csrf" not in session: diff --git a/rophako/www/errors/500.html b/rophako/www/errors/500.html new file mode 100644 index 0000000..b1b7c27 --- /dev/null +++ b/rophako/www/errors/500.html @@ -0,0 +1,10 @@ +{% extends "layout.html" %} +{% block title %}Internal Server Error{% endblock %} +{% block content %} + +

Internal Server Error

+ +This website has experienced an error. The site admin has been sent an e-mail +about this. Please retry your request sometime later. + +{% endblock %} diff --git a/runserver.py b/runserver.py index ea9f846..8d37d05 100644 --- a/runserver.py +++ b/runserver.py @@ -25,12 +25,17 @@ parser.add_argument( type=str, help="SSL certificate file.", ) +parser.add_argument( + "--production", + help="Turns off debug mode, runs as if in production.", + action="store_true", +) args = parser.parse_args() if __name__ == '__main__': flask_options = dict( host='0.0.0.0', - debug=True, + debug=not args.production, port=args.port, threaded=True, )