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:
Russell Keith-Magee 2011-02-01 14:18:07 +00:00
parent 00fda7f45d
commit 9a82eb6ff1
3 changed files with 116 additions and 20 deletions

View File

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

View File

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

View File

@ -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&#39;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&#39;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&#39;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&#39;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)