From 9a82eb6ff109e95dbb6cf26ee3d2ce1c548714bb Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee <russell@keith-magee.com> Date: Tue, 1 Feb 2011 14:18:07 +0000 Subject: [PATCH] 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 --- django/utils/log.py | 2 +- django/views/debug.py | 45 ++++++----- tests/regressiontests/views/tests/debug.py | 89 +++++++++++++++++++++- 3 files changed, 116 insertions(+), 20 deletions(-) diff --git a/django/utils/log.py b/django/utils/log.py index 1f6dc52849..784035b3cf 100644 --- a/django/utils/log.py +++ b/django/utils/log.py @@ -83,7 +83,7 @@ class AdminEmailHandler(logging.Handler): exc_info = record.exc_info stack_trace = '\n'.join(traceback.format_exception(*record.exc_info)) else: - exc_info = () + exc_info = (None, record.msg, None) stack_trace = 'No stack trace available' message = "%s\n\n%s" % (stack_trace, request_repr) diff --git a/django/views/debug.py b/django/views/debug.py index 6bb6a0bd93..569cec18df 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -59,7 +59,7 @@ def technical_500_response(request, exc_type, exc_value, tb): html = reporter.get_traceback_html() return HttpResponseServerError(html, mimetype='text/html') -class ExceptionReporter: +class ExceptionReporter(object): """ A class to organize and coordinate reporting on exceptions. """ @@ -82,7 +82,7 @@ class ExceptionReporter: def get_traceback_html(self): "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 self.template_does_not_exist = True self.loader_debug_info = [] @@ -113,11 +113,12 @@ class ExceptionReporter: frames = self.get_traceback_frames() for i, frame in enumerate(frames): - frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']] + if 'vars' in frame: + frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']] frames[i] = frame 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) end = getattr(self.exc_value, 'end', 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') c = Context({ 'is_email': self.is_email, - 'exception_type': self.exc_type.__name__, - 'exception_value': smart_unicode(self.exc_value, errors='replace'), 'unicode_hint': unicode_hint, 'frames': frames, - 'lastframe': frames[-1], 'request': self.request, 'settings': get_safe_settings(), 'sys_executable': sys.executable, @@ -143,6 +141,13 @@ class ExceptionReporter: 'template_does_not_exist': self.template_does_not_exist, '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) def get_template_exception_info(self): @@ -250,14 +255,6 @@ class ExceptionReporter: }) tb = tb.tb_next - if not frames: - frames = [{ - 'filename': '<unknown>', - 'function': '?', - 'lineno': '?', - 'context_line': '???', - }] - return frames def format_exception(self): @@ -319,7 +316,7 @@ TECHNICAL_500_TEMPLATE = """ <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <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"> html * { padding:0; margin:0; } body * { padding:10px 20px; } @@ -429,9 +426,10 @@ TECHNICAL_500_TEMPLATE = """ </head> <body> <div id="summary"> - <h1>{{ exception_type }}{% if request %} at {{ request.path_info|escape }}{% endif %}</h1> - <pre class="exception_value">{{ exception_value|force_escape }}</pre> + <h1>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %}</h1> + <pre class="exception_value">{% if exception_value %}{{ exception_value|force_escape }}{% else %}No exception supplied{% endif %}</pre> <table class="meta"> +{% if request %} <tr> <th>Request Method:</th> <td>{{ request.META.REQUEST_METHOD }}</td> @@ -440,22 +438,29 @@ TECHNICAL_500_TEMPLATE = """ <th>Request URL:</th> <td>{{ request.build_absolute_uri|escape }}</td> </tr> +{% endif %} <tr> <th>Django Version:</th> <td>{{ django_version_info }}</td> </tr> +{% if exception_type %} <tr> <th>Exception Type:</th> <td>{{ exception_type }}</td> </tr> +{% endif %} +{% if exception_type and exception_value %} <tr> <th>Exception Value:</th> <td><pre>{{ exception_value|force_escape }}</pre></td> </tr> +{% endif %} +{% if lastframe %} <tr> <th>Exception Location:</th> <td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td> </tr> +{% endif %} <tr> <th>Python Executable:</th> <td>{{ sys_executable|escape }}</td> @@ -515,6 +520,7 @@ TECHNICAL_500_TEMPLATE = """ </table> </div> {% endif %} +{% if frames %} <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> {% autoescape off %} @@ -615,6 +621,7 @@ Exception Value: {{ exception_value|force_escape }} </form> </div> {% endif %} +{% endif %} <div id="requestinfo"> <h2>Request information</h2> @@ -725,6 +732,8 @@ Exception Value: {{ exception_value|force_escape }} {% endfor %} </tbody> </table> +{% else %} + <p>Request data not supplied</p> {% endif %} <h3 id="settings-info">Settings</h3> diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py index c71e8c502d..0b07b406d0 100644 --- a/tests/regressiontests/views/tests/debug.py +++ b/tests/regressiontests/views/tests/debug.py @@ -1,13 +1,16 @@ import inspect +import sys from django.conf import settings 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.template import TemplateSyntaxError +from django.views.debug import ExceptionReporter from regressiontests.views import BrokenException, except_args + class DebugViewTests(TestCase): def setUp(self): self.old_debug = settings.DEBUG @@ -52,3 +55,87 @@ class DebugViewTests(TestCase): def test_template_loader_postmortem(self): 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) + + +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)