Fixed #14972 -- Ensure that the HTML email logger always produces useful output, regardless of whether it has been given an exception or a request. Thanks to jamstooks for the report, and bpeschier for the initial patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@15383 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
00fda7f45d
commit
9a82eb6ff1
|
@ -83,7 +83,7 @@ class AdminEmailHandler(logging.Handler):
|
||||||
exc_info = record.exc_info
|
exc_info = record.exc_info
|
||||||
stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
|
stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
|
||||||
else:
|
else:
|
||||||
exc_info = ()
|
exc_info = (None, record.msg, None)
|
||||||
stack_trace = 'No stack trace available'
|
stack_trace = 'No stack trace available'
|
||||||
|
|
||||||
message = "%s\n\n%s" % (stack_trace, request_repr)
|
message = "%s\n\n%s" % (stack_trace, request_repr)
|
||||||
|
|
|
@ -59,7 +59,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
|
||||||
html = reporter.get_traceback_html()
|
html = reporter.get_traceback_html()
|
||||||
return HttpResponseServerError(html, mimetype='text/html')
|
return HttpResponseServerError(html, mimetype='text/html')
|
||||||
|
|
||||||
class ExceptionReporter:
|
class ExceptionReporter(object):
|
||||||
"""
|
"""
|
||||||
A class to organize and coordinate reporting on exceptions.
|
A class to organize and coordinate reporting on exceptions.
|
||||||
"""
|
"""
|
||||||
|
@ -82,7 +82,7 @@ class ExceptionReporter:
|
||||||
def get_traceback_html(self):
|
def get_traceback_html(self):
|
||||||
"Return HTML code for traceback."
|
"Return HTML code for traceback."
|
||||||
|
|
||||||
if issubclass(self.exc_type, TemplateDoesNotExist):
|
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
|
||||||
from django.template.loader import template_source_loaders
|
from django.template.loader import template_source_loaders
|
||||||
self.template_does_not_exist = True
|
self.template_does_not_exist = True
|
||||||
self.loader_debug_info = []
|
self.loader_debug_info = []
|
||||||
|
@ -113,11 +113,12 @@ class ExceptionReporter:
|
||||||
|
|
||||||
frames = self.get_traceback_frames()
|
frames = self.get_traceback_frames()
|
||||||
for i, frame in enumerate(frames):
|
for i, frame in enumerate(frames):
|
||||||
|
if 'vars' in frame:
|
||||||
frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']]
|
frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']]
|
||||||
frames[i] = frame
|
frames[i] = frame
|
||||||
|
|
||||||
unicode_hint = ''
|
unicode_hint = ''
|
||||||
if issubclass(self.exc_type, UnicodeError):
|
if self.exc_type and issubclass(self.exc_type, UnicodeError):
|
||||||
start = getattr(self.exc_value, 'start', None)
|
start = getattr(self.exc_value, 'start', None)
|
||||||
end = getattr(self.exc_value, 'end', None)
|
end = getattr(self.exc_value, 'end', None)
|
||||||
if start is not None and end is not None:
|
if start is not None and end is not None:
|
||||||
|
@ -127,11 +128,8 @@ class ExceptionReporter:
|
||||||
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
|
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
|
||||||
c = Context({
|
c = Context({
|
||||||
'is_email': self.is_email,
|
'is_email': self.is_email,
|
||||||
'exception_type': self.exc_type.__name__,
|
|
||||||
'exception_value': smart_unicode(self.exc_value, errors='replace'),
|
|
||||||
'unicode_hint': unicode_hint,
|
'unicode_hint': unicode_hint,
|
||||||
'frames': frames,
|
'frames': frames,
|
||||||
'lastframe': frames[-1],
|
|
||||||
'request': self.request,
|
'request': self.request,
|
||||||
'settings': get_safe_settings(),
|
'settings': get_safe_settings(),
|
||||||
'sys_executable': sys.executable,
|
'sys_executable': sys.executable,
|
||||||
|
@ -143,6 +141,13 @@ class ExceptionReporter:
|
||||||
'template_does_not_exist': self.template_does_not_exist,
|
'template_does_not_exist': self.template_does_not_exist,
|
||||||
'loader_debug_info': self.loader_debug_info,
|
'loader_debug_info': self.loader_debug_info,
|
||||||
})
|
})
|
||||||
|
# Check whether exception info is available
|
||||||
|
if self.exc_type:
|
||||||
|
c['exception_type'] = self.exc_type.__name__
|
||||||
|
if self.exc_value:
|
||||||
|
c['exception_value'] = smart_unicode(self.exc_value, errors='replace')
|
||||||
|
if frames:
|
||||||
|
c['lastframe'] = frames[-1]
|
||||||
return t.render(c)
|
return t.render(c)
|
||||||
|
|
||||||
def get_template_exception_info(self):
|
def get_template_exception_info(self):
|
||||||
|
@ -250,14 +255,6 @@ class ExceptionReporter:
|
||||||
})
|
})
|
||||||
tb = tb.tb_next
|
tb = tb.tb_next
|
||||||
|
|
||||||
if not frames:
|
|
||||||
frames = [{
|
|
||||||
'filename': '<unknown>',
|
|
||||||
'function': '?',
|
|
||||||
'lineno': '?',
|
|
||||||
'context_line': '???',
|
|
||||||
}]
|
|
||||||
|
|
||||||
return frames
|
return frames
|
||||||
|
|
||||||
def format_exception(self):
|
def format_exception(self):
|
||||||
|
@ -319,7 +316,7 @@ TECHNICAL_500_TEMPLATE = """
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||||
<meta name="robots" content="NONE,NOARCHIVE">
|
<meta name="robots" content="NONE,NOARCHIVE">
|
||||||
<title>{{ exception_type }} at {{ request.path_info|escape }}</title>
|
<title>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %}</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
html * { padding:0; margin:0; }
|
html * { padding:0; margin:0; }
|
||||||
body * { padding:10px 20px; }
|
body * { padding:10px 20px; }
|
||||||
|
@ -429,9 +426,10 @@ TECHNICAL_500_TEMPLATE = """
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="summary">
|
<div id="summary">
|
||||||
<h1>{{ exception_type }}{% if request %} at {{ request.path_info|escape }}{% endif %}</h1>
|
<h1>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %}</h1>
|
||||||
<pre class="exception_value">{{ exception_value|force_escape }}</pre>
|
<pre class="exception_value">{% if exception_value %}{{ exception_value|force_escape }}{% else %}No exception supplied{% endif %}</pre>
|
||||||
<table class="meta">
|
<table class="meta">
|
||||||
|
{% if request %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Request Method:</th>
|
<th>Request Method:</th>
|
||||||
<td>{{ request.META.REQUEST_METHOD }}</td>
|
<td>{{ request.META.REQUEST_METHOD }}</td>
|
||||||
|
@ -440,22 +438,29 @@ TECHNICAL_500_TEMPLATE = """
|
||||||
<th>Request URL:</th>
|
<th>Request URL:</th>
|
||||||
<td>{{ request.build_absolute_uri|escape }}</td>
|
<td>{{ request.build_absolute_uri|escape }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Django Version:</th>
|
<th>Django Version:</th>
|
||||||
<td>{{ django_version_info }}</td>
|
<td>{{ django_version_info }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if exception_type %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Exception Type:</th>
|
<th>Exception Type:</th>
|
||||||
<td>{{ exception_type }}</td>
|
<td>{{ exception_type }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if exception_type and exception_value %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Exception Value:</th>
|
<th>Exception Value:</th>
|
||||||
<td><pre>{{ exception_value|force_escape }}</pre></td>
|
<td><pre>{{ exception_value|force_escape }}</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if lastframe %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Exception Location:</th>
|
<th>Exception Location:</th>
|
||||||
<td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td>
|
<td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Python Executable:</th>
|
<th>Python Executable:</th>
|
||||||
<td>{{ sys_executable|escape }}</td>
|
<td>{{ sys_executable|escape }}</td>
|
||||||
|
@ -515,6 +520,7 @@ TECHNICAL_500_TEMPLATE = """
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if frames %}
|
||||||
<div id="traceback">
|
<div id="traceback">
|
||||||
<h2>Traceback <span class="commands">{% if not is_email %}<a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></span>{% endif %}</h2>
|
<h2>Traceback <span class="commands">{% if not is_email %}<a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></span>{% endif %}</h2>
|
||||||
{% autoescape off %}
|
{% autoescape off %}
|
||||||
|
@ -615,6 +621,7 @@ Exception Value: {{ exception_value|force_escape }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div id="requestinfo">
|
<div id="requestinfo">
|
||||||
<h2>Request information</h2>
|
<h2>Request information</h2>
|
||||||
|
@ -725,6 +732,8 @@ Exception Value: {{ exception_value|force_escape }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>Request data not supplied</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h3 id="settings-info">Settings</h3>
|
<h3 id="settings-info">Settings</h3>
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import inspect
|
import inspect
|
||||||
|
import sys
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.test import TestCase
|
from django.test import TestCase, RequestFactory
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.template import TemplateSyntaxError
|
from django.template import TemplateSyntaxError
|
||||||
|
from django.views.debug import ExceptionReporter
|
||||||
|
|
||||||
from regressiontests.views import BrokenException, except_args
|
from regressiontests.views import BrokenException, except_args
|
||||||
|
|
||||||
|
|
||||||
class DebugViewTests(TestCase):
|
class DebugViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.old_debug = settings.DEBUG
|
self.old_debug = settings.DEBUG
|
||||||
|
@ -52,3 +55,87 @@ class DebugViewTests(TestCase):
|
||||||
def test_template_loader_postmortem(self):
|
def test_template_loader_postmortem(self):
|
||||||
response = self.client.get(reverse('raises_template_does_not_exist'))
|
response = self.client.get(reverse('raises_template_does_not_exist'))
|
||||||
self.assertContains(response, 'templates/i_dont_exist.html</code> (File does not exist)</li>', status_code=500)
|
self.assertContains(response, 'templates/i_dont_exist.html</code> (File does not exist)</li>', status_code=500)
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionReporterTests(TestCase):
|
||||||
|
rf = RequestFactory()
|
||||||
|
|
||||||
|
def test_request_and_exception(self):
|
||||||
|
"A simple exception report can be generated"
|
||||||
|
try:
|
||||||
|
request = self.rf.get('/test_view/')
|
||||||
|
raise KeyError("Can't find my keys")
|
||||||
|
except KeyError:
|
||||||
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
||||||
|
html = reporter.get_traceback_html()
|
||||||
|
self.assertIn('<h1>KeyError at /test_view/</h1>', html)
|
||||||
|
self.assertIn('<pre class="exception_value">Can't find my keys</pre>', html)
|
||||||
|
self.assertIn('<th>Request Method:</th>', html)
|
||||||
|
self.assertIn('<th>Request URL:</th>', html)
|
||||||
|
self.assertIn('<th>Exception Type:</th>', html)
|
||||||
|
self.assertIn('<th>Exception Value:</th>', html)
|
||||||
|
self.assertIn('<h2>Traceback ', html)
|
||||||
|
self.assertIn('<h2>Request information</h2>', html)
|
||||||
|
self.assertNotIn('<p>Request data not supplied</p>', html)
|
||||||
|
|
||||||
|
def test_no_request(self):
|
||||||
|
"An exception report can be generated without request"
|
||||||
|
try:
|
||||||
|
raise KeyError("Can't find my keys")
|
||||||
|
except KeyError:
|
||||||
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
||||||
|
html = reporter.get_traceback_html()
|
||||||
|
self.assertIn('<h1>KeyError</h1>', html)
|
||||||
|
self.assertIn('<pre class="exception_value">Can't find my keys</pre>', html)
|
||||||
|
self.assertNotIn('<th>Request Method:</th>', html)
|
||||||
|
self.assertNotIn('<th>Request URL:</th>', html)
|
||||||
|
self.assertIn('<th>Exception Type:</th>', html)
|
||||||
|
self.assertIn('<th>Exception Value:</th>', html)
|
||||||
|
self.assertIn('<h2>Traceback ', html)
|
||||||
|
self.assertIn('<h2>Request information</h2>', html)
|
||||||
|
self.assertIn('<p>Request data not supplied</p>', html)
|
||||||
|
|
||||||
|
def test_no_exception(self):
|
||||||
|
"An exception report can be generated for just a request"
|
||||||
|
request = self.rf.get('/test_view/')
|
||||||
|
reporter = ExceptionReporter(request, None, None, None)
|
||||||
|
html = reporter.get_traceback_html()
|
||||||
|
self.assertIn('<h1>Report at /test_view/</h1>', html)
|
||||||
|
self.assertIn('<pre class="exception_value">No exception supplied</pre>', html)
|
||||||
|
self.assertIn('<th>Request Method:</th>', html)
|
||||||
|
self.assertIn('<th>Request URL:</th>', html)
|
||||||
|
self.assertNotIn('<th>Exception Type:</th>', html)
|
||||||
|
self.assertNotIn('<th>Exception Value:</th>', html)
|
||||||
|
self.assertNotIn('<h2>Traceback ', html)
|
||||||
|
self.assertIn('<h2>Request information</h2>', html)
|
||||||
|
self.assertNotIn('<p>Request data not supplied</p>', html)
|
||||||
|
|
||||||
|
def test_request_and_message(self):
|
||||||
|
"A message can be provided in addition to a request"
|
||||||
|
request = self.rf.get('/test_view/')
|
||||||
|
reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
|
||||||
|
html = reporter.get_traceback_html()
|
||||||
|
self.assertIn('<h1>Report at /test_view/</h1>', html)
|
||||||
|
self.assertIn('<pre class="exception_value">I'm a little teapot</pre>', html)
|
||||||
|
self.assertIn('<th>Request Method:</th>', html)
|
||||||
|
self.assertIn('<th>Request URL:</th>', html)
|
||||||
|
self.assertNotIn('<th>Exception Type:</th>', html)
|
||||||
|
self.assertNotIn('<th>Exception Value:</th>', html)
|
||||||
|
self.assertNotIn('<h2>Traceback ', html)
|
||||||
|
self.assertIn('<h2>Request information</h2>', html)
|
||||||
|
self.assertNotIn('<p>Request data not supplied</p>', html)
|
||||||
|
|
||||||
|
def test_message_only(self):
|
||||||
|
reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
|
||||||
|
html = reporter.get_traceback_html()
|
||||||
|
self.assertIn('<h1>Report</h1>', html)
|
||||||
|
self.assertIn('<pre class="exception_value">I'm a little teapot</pre>', html)
|
||||||
|
self.assertNotIn('<th>Request Method:</th>', html)
|
||||||
|
self.assertNotIn('<th>Request URL:</th>', html)
|
||||||
|
self.assertNotIn('<th>Exception Type:</th>', html)
|
||||||
|
self.assertNotIn('<th>Exception Value:</th>', html)
|
||||||
|
self.assertNotIn('<h2>Traceback ', html)
|
||||||
|
self.assertIn('<h2>Request information</h2>', html)
|
||||||
|
self.assertIn('<p>Request data not supplied</p>', html)
|
||||||
|
|
Loading…
Reference in New Issue