forked from edward/owl-map
		
	Improve error handling
* Report errors to admin by e-mail * Return details of error in JSON for API calls * Show error details page for non-API calls
This commit is contained in:
		
							parent
							
								
									db7b13b706
								
							
						
					
					
						commit
						7a3cb474ac
					
				
							
								
								
									
										46
									
								
								matcher/error_mail.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								matcher/error_mail.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
import logging
 | 
			
		||||
from logging.handlers import SMTPHandler
 | 
			
		||||
from logging import Formatter
 | 
			
		||||
from flask import request
 | 
			
		||||
 | 
			
		||||
PROJECT = 'osm-wikidata'
 | 
			
		||||
 | 
			
		||||
class MatcherSMTPHandler(SMTPHandler):
 | 
			
		||||
    def getSubject(self, record):  # noqa: N802
 | 
			
		||||
        return (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}')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestFormatter(Formatter):
 | 
			
		||||
    def format(self, record):
 | 
			
		||||
        record.request = request
 | 
			
		||||
        return super().format(record)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_error_mail(app):
 | 
			
		||||
    if not app.config.get('ERROR_MAIL'):
 | 
			
		||||
        return
 | 
			
		||||
    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 = MatcherSMTPHandler(app.config['SMTP_HOST'],
 | 
			
		||||
                                      app.config['MAIL_FROM'],
 | 
			
		||||
                                      app.config['ADMINS'],
 | 
			
		||||
                                      app.name + ' error')
 | 
			
		||||
    mail_handler.setFormatter(formatter)
 | 
			
		||||
 | 
			
		||||
    mail_handler.setLevel(logging.ERROR)
 | 
			
		||||
    app.logger.propagate = True
 | 
			
		||||
    app.logger.addHandler(mail_handler)
 | 
			
		||||
							
								
								
									
										33
									
								
								web_view.py
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								web_view.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -5,12 +5,14 @@ from flask import (Flask, render_template, request, jsonify, redirect, url_for,
 | 
			
		|||
from sqlalchemy import func
 | 
			
		||||
from sqlalchemy.sql.expression import update
 | 
			
		||||
from matcher import (nominatim, model, database, commons, wikidata, wikidata_api,
 | 
			
		||||
                     osm_oauth, edit, mail, api)
 | 
			
		||||
                     osm_oauth, edit, mail, api, error_mail)
 | 
			
		||||
from werkzeug.debug.tbtools import get_current_traceback
 | 
			
		||||
from matcher.data import property_map
 | 
			
		||||
from time import time, sleep
 | 
			
		||||
from requests_oauthlib import OAuth1Session
 | 
			
		||||
from lxml import etree
 | 
			
		||||
from sqlalchemy.orm.attributes import flag_modified
 | 
			
		||||
import werkzeug.exceptions
 | 
			
		||||
import inspect
 | 
			
		||||
import flask_login
 | 
			
		||||
import requests
 | 
			
		||||
import json
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +26,7 @@ re_point = re.compile(r'^POINT\((.+) (.+)\)$')
 | 
			
		|||
app = Flask(__name__)
 | 
			
		||||
app.debug = True
 | 
			
		||||
app.config.from_object('config.default')
 | 
			
		||||
error_mail.setup_error_mail(app)
 | 
			
		||||
 | 
			
		||||
login_manager = flask_login.LoginManager(app)
 | 
			
		||||
login_manager.login_view = 'login_route'
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +50,32 @@ def shutdown_session(exception=None):
 | 
			
		|||
def global_user():
 | 
			
		||||
    g.user = flask_login.current_user._get_current_object()
 | 
			
		||||
 | 
			
		||||
def dict_repr_values(d):
 | 
			
		||||
    return {key: repr(value) for key, value in d.items()}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.errorhandler(werkzeug.exceptions.InternalServerError)
 | 
			
		||||
def exception_handler(e):
 | 
			
		||||
    tb = get_current_traceback()
 | 
			
		||||
    last_frame = next(frame for frame in reversed(tb.frames) if not frame.is_library)
 | 
			
		||||
    last_frame_args = inspect.getargs(last_frame.code)
 | 
			
		||||
    if request.path.startswith("/api/"):
 | 
			
		||||
        return cors_jsonify({
 | 
			
		||||
            "success": False,
 | 
			
		||||
            "error": tb.exception,
 | 
			
		||||
            "traceback": tb.plaintext,
 | 
			
		||||
            "locals": dict_repr_values(last_frame.locals),
 | 
			
		||||
            "last_function": {
 | 
			
		||||
                "name": tb.frames[-1].function_name,
 | 
			
		||||
                "args": repr(last_frame_args),
 | 
			
		||||
            },
 | 
			
		||||
        }), 500
 | 
			
		||||
 | 
			
		||||
    return render_template('show_error.html',
 | 
			
		||||
                           tb=tb,
 | 
			
		||||
                           last_frame=last_frame,
 | 
			
		||||
                           last_frame_args=last_frame_args), 500
 | 
			
		||||
 | 
			
		||||
def cors_jsonify(*args, **kwargs):
 | 
			
		||||
    response = jsonify(*args, **kwargs)
 | 
			
		||||
    response.headers["Access-Control-Allow-Origin"] = "*"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue