Merge pull request #291 from subyraman/master
Add rich HTML traceback in debug mode, add HTML 500 page in prod
This commit is contained in:
commit
9108a4c69f
|
@ -12,3 +12,4 @@ kyoukai
|
||||||
falcon
|
falcon
|
||||||
tornado
|
tornado
|
||||||
aiofiles
|
aiofiles
|
||||||
|
beautifulsoup4
|
||||||
|
|
|
@ -1,6 +1,104 @@
|
||||||
from .response import text
|
from .response import text, html
|
||||||
from .log import log
|
from .log import log
|
||||||
from traceback import format_exc
|
from traceback import format_exc, extract_tb
|
||||||
|
import sys
|
||||||
|
|
||||||
|
TRACEBACK_STYLE = '''
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 20px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 code {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-line > * {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-line {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-code {
|
||||||
|
font-size: 16px;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tb-wrapper {
|
||||||
|
border: 1px solid #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tb-header {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-descriptor {
|
||||||
|
background-color: #e2eafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-descriptor {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
'''
|
||||||
|
|
||||||
|
TRACEBACK_WRAPPER_HTML = '''
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{style}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{exc_name}</h1>
|
||||||
|
<h3><code>{exc_value}</code></h3>
|
||||||
|
<div class="tb-wrapper">
|
||||||
|
<p class="tb-header">Traceback (most recent call last):</p>
|
||||||
|
{frame_html}
|
||||||
|
<p class="summary">
|
||||||
|
<b>{exc_name}: {exc_value}</b>
|
||||||
|
while handling uri <code>{uri}</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
|
||||||
|
TRACEBACK_LINE_HTML = '''
|
||||||
|
<div class="frame-line">
|
||||||
|
<p class="frame-descriptor">
|
||||||
|
File {0.filename}, line <i>{0.lineno}</i>,
|
||||||
|
in <code><b>{0.name}</b></code>
|
||||||
|
</p>
|
||||||
|
<p class="frame-code"><code>{0.line}</code></p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
INTERNAL_SERVER_ERROR_HTML = '''
|
||||||
|
<h1>Internal Server Error</h1>
|
||||||
|
<p>
|
||||||
|
The server encountered an internal error and cannot complete
|
||||||
|
your request.
|
||||||
|
</p>
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
class SanicException(Exception):
|
class SanicException(Exception):
|
||||||
|
@ -46,6 +144,21 @@ class Handler:
|
||||||
self.handlers = {}
|
self.handlers = {}
|
||||||
self.sanic = sanic
|
self.sanic = sanic
|
||||||
|
|
||||||
|
def _render_traceback_html(self, exception, request):
|
||||||
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
|
frames = extract_tb(tb)
|
||||||
|
|
||||||
|
frame_html = []
|
||||||
|
for frame in frames:
|
||||||
|
frame_html.append(TRACEBACK_LINE_HTML.format(frame))
|
||||||
|
|
||||||
|
return TRACEBACK_WRAPPER_HTML.format(
|
||||||
|
style=TRACEBACK_STYLE,
|
||||||
|
exc_name=exc_type.__name__,
|
||||||
|
exc_value=exc_value,
|
||||||
|
frame_html=''.join(frame_html),
|
||||||
|
uri=request.url)
|
||||||
|
|
||||||
def add(self, exception, handler):
|
def add(self, exception, handler):
|
||||||
self.handlers[exception] = handler
|
self.handlers[exception] = handler
|
||||||
|
|
||||||
|
@ -77,11 +190,12 @@ class Handler:
|
||||||
'Error: {}'.format(exception),
|
'Error: {}'.format(exception),
|
||||||
status=getattr(exception, 'status_code', 500))
|
status=getattr(exception, 'status_code', 500))
|
||||||
elif self.sanic.debug:
|
elif self.sanic.debug:
|
||||||
|
html_output = self._render_traceback_html(exception, request)
|
||||||
|
|
||||||
response_message = (
|
response_message = (
|
||||||
'Exception occurred while handling uri: "{}"\n{}'.format(
|
'Exception occurred while handling uri: "{}"\n{}'.format(
|
||||||
request.url, format_exc()))
|
request.url, format_exc()))
|
||||||
log.error(response_message)
|
log.error(response_message)
|
||||||
return text(response_message, status=500)
|
return html(html_output, status=500)
|
||||||
else:
|
else:
|
||||||
return text(
|
return html(INTERNAL_SERVER_ERROR_HTML, status=500)
|
||||||
'An error occurred while generating the response', status=500)
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
@ -75,7 +76,13 @@ def test_handled_unhandled_exception(exception_app):
|
||||||
request, response = sanic_endpoint_test(
|
request, response = sanic_endpoint_test(
|
||||||
exception_app, uri='/divide_by_zero')
|
exception_app, uri='/divide_by_zero')
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
assert response.body == b'An error occurred while generating the response'
|
soup = BeautifulSoup(response.body, 'html.parser')
|
||||||
|
assert soup.h1.text == 'Internal Server Error'
|
||||||
|
|
||||||
|
message = " ".join(soup.p.text.split())
|
||||||
|
assert message == (
|
||||||
|
"The server encountered an internal error and "
|
||||||
|
"cannot complete your request.")
|
||||||
|
|
||||||
|
|
||||||
def test_exception_in_exception_handler(exception_app):
|
def test_exception_in_exception_handler(exception_app):
|
||||||
|
|
|
@ -2,6 +2,7 @@ from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.exceptions import InvalidUsage, ServerError, NotFound
|
from sanic.exceptions import InvalidUsage, ServerError, NotFound
|
||||||
from sanic.utils import sanic_endpoint_test
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
exception_handler_app = Sanic('test_exception_handler')
|
exception_handler_app = Sanic('test_exception_handler')
|
||||||
|
|
||||||
|
@ -21,6 +22,12 @@ def handler_3(request):
|
||||||
raise NotFound("OK")
|
raise NotFound("OK")
|
||||||
|
|
||||||
|
|
||||||
|
@exception_handler_app.route('/4')
|
||||||
|
def handler_4(request):
|
||||||
|
foo = bar
|
||||||
|
return text(foo)
|
||||||
|
|
||||||
|
|
||||||
@exception_handler_app.exception(NotFound, ServerError)
|
@exception_handler_app.exception(NotFound, ServerError)
|
||||||
def handler_exception(request, exception):
|
def handler_exception(request, exception):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
@ -47,3 +54,20 @@ def test_text_exception__handler():
|
||||||
exception_handler_app, uri='/random')
|
exception_handler_app, uri='/random')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_traceback_output_in_debug_mode():
|
||||||
|
request, response = sanic_endpoint_test(
|
||||||
|
exception_handler_app, uri='/4', debug=True)
|
||||||
|
assert response.status == 500
|
||||||
|
soup = BeautifulSoup(response.body, 'html.parser')
|
||||||
|
html = str(soup)
|
||||||
|
|
||||||
|
assert 'response = handler(request, *args, **kwargs)' in html
|
||||||
|
assert 'handler_4' in html
|
||||||
|
assert 'foo = bar' in html
|
||||||
|
|
||||||
|
summary_text = " ".join(soup.select('.summary')[0].text.split())
|
||||||
|
assert (
|
||||||
|
"NameError: name 'bar' "
|
||||||
|
"is not defined while handling uri /4") == summary_text
|
||||||
|
|
Loading…
Reference in New Issue
Block a user