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:
Eli Uriegas 2017-01-17 15:47:37 -06:00 committed by GitHub
commit 9108a4c69f
5 changed files with 153 additions and 6 deletions

View File

@ -12,3 +12,4 @@ kyoukai
falcon falcon
tornado tornado
aiofiles aiofiles
beautifulsoup4

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ python =
deps = deps =
aiohttp aiohttp
pytest pytest
beautifulsoup4
commands = commands =
pytest tests {posargs} pytest tests {posargs}