Browse Source

Add exception e-mail handler

pull/2/head
Noah Petherbridge 5 years ago
parent
commit
45eec97bf3
5 changed files with 90 additions and 3 deletions
  1. 11
    0
      rophako/app.py
  2. 5
    1
      rophako/jsondb.py
  3. 58
    1
      rophako/utils.py
  4. 10
    0
      rophako/www/errors/500.html
  5. 6
    1
      runserver.py

+ 11
- 0
rophako/app.py View File

@@ -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

+ 5
- 1
rophako/jsondb.py View File

@@ -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

+ 58
- 1
rophako/utils.py View File

@@ -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:

+ 10
- 0
rophako/www/errors/500.html View File

@@ -0,0 +1,10 @@
{% extends "layout.html" %}
{% block title %}Internal Server Error{% endblock %}
{% block content %}

<h1>Internal Server Error</h1>

This website has experienced an error. The site admin has been sent an e-mail
about this. Please retry your request sometime later.

{% endblock %}

+ 6
- 1
runserver.py View File

@@ -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,
)

Loading…
Cancel
Save