/
error_utils.py
116 lines (96 loc) · 4.08 KB
/
error_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import re
import sys
import traceback
from flask import Flask, jsonify, current_app, request
from werkzeug.exceptions import (
HTTPException,
InternalServerError,
BadRequest,
NotFound,
)
from sqlalchemy.orm import Query
def log_error(exc: Exception, error_msg: str):
"""Collect meta data about the exception and log it.
error_msg comes in as an extra attribute because Exception implementations differ here."""
exc_info = sys.exc_info()
last_traceback = exc_info[2]
if hasattr(exc, "__cause__") and exc.__cause__ is not None:
exc_info = (exc.__cause__.__class__, exc.__cause__, last_traceback)
extra = dict(url=request.path, **get_err_source_info(last_traceback))
msg = (
'{error_name}:"{message}" [occurred at {src_module}({src_func}):{src_linenr},'
"URL was: {url}]".format(
error_name=exc.__class__.__name__, message=error_msg, **extra
)
)
current_app.logger.error(msg, exc_info=exc_info)
def get_err_source_info(original_traceback=None) -> dict:
"""Use this when an error is handled to get info on where it occurred."""
try: # carefully try to get the actual place where the error happened
if not original_traceback:
original_traceback = sys.exc_info()[2] # class, exc, traceback
first_call = traceback.extract_tb(original_traceback)[-1]
return dict(
src_module=first_call[0],
src_linenr=first_call[1],
src_func=first_call[2],
src_code=first_call[3],
)
except Exception as e:
current_app.warning(
"I was unable to retrieve error source information: %s." % str(e)
)
return dict(module="", linenr=0, method="", src_code="")
def error_handling_router(error: HTTPException):
"""
Generic handler for errors.
We respond in json if the request content-type is JSON.
The ui package can also define how it wants to render HTML errors, by setting a function.
"""
log_error(error, getattr(error, "description", str(error)))
http_error_code = 500 # fallback
if hasattr(error, "code"):
try:
http_error_code = int(error.code)
except ValueError:
pass
error_text = getattr(
error, "description", f"Something went wrong: {error.__class__.__name__}"
)
if request.is_json:
response = jsonify(
dict(
message=getattr(error, "description", str(error)),
status=http_error_code,
)
)
response.status_code = http_error_code
return response
# Can UI handle this specific type?
elif hasattr(current_app, "%s_handler_html" % error.__class__.__name__):
return getattr(current_app, "%s_handler_html" % error.__class__.__name__)(error)
# Can UI handle HTTPException? Let's make one from the error.
elif hasattr(current_app, "HttpException_handler_html"):
return current_app.HttpException_handler_html(error)
# This fallback is ugly but better than nothing.
else:
return "%s:%s" % (error.__class__.__name__, error_text), http_error_code
def add_basic_error_handlers(app: Flask):
"""Register classes we care about with the generic handler."""
app.register_error_handler(InternalServerError, error_handling_router)
app.register_error_handler(BadRequest, error_handling_router)
app.register_error_handler(HTTPException, error_handling_router)
app.register_error_handler(NotFound, error_handling_router)
app.register_error_handler(Exception, error_handling_router)
def print_query(query: Query) -> str:
"""Print full SQLAlchemy query with compiled parameters.
Recommended use as developer tool only.
Adapted from https://stackoverflow.com/a/63900851/13775459
"""
regex = re.compile(r":(?P<name>\w+)")
params = query.statement.compile().params
sql = regex.sub(r"'{\g<name>}'", str(query.statement)).format(**params)
from flexmeasures.data.config import db
print(f"\nPrinting SQLAlchemy query to database {db.engine.url.database}:\n\n")
print(sql)
return sql