Fixed #30752 -- Allowed using ExceptionReporter subclasses in error reports.

This commit is contained in:
Pavel Lysak 2019-09-07 20:08:12 +03:00 committed by Carlton Gibson
parent a5a28de89d
commit 13e4abf83e
9 changed files with 112 additions and 3 deletions

View File

@ -567,6 +567,10 @@ LOGGING_CONFIG = 'logging.config.dictConfig'
# Custom logging configuration.
LOGGING = {}
# Default exception reporter class used in case none has been
# specifically assigned to the HttpRequest instance.
DEFAULT_EXCEPTION_REPORTER = 'django.views.debug.ExceptionReporter'
# Default exception reporter filter class used in case none has been
# specifically assigned to the HttpRequest instance.
DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFilter'

View File

@ -86,7 +86,7 @@ class AdminEmailHandler(logging.Handler):
super().__init__()
self.include_html = include_html
self.email_backend = email_backend
self.reporter_class = import_string(reporter_class or 'django.views.debug.ExceptionReporter')
self.reporter_class = import_string(reporter_class or settings.DEFAULT_EXCEPTION_REPORTER)
def emit(self, record):
try:

View File

@ -47,7 +47,7 @@ def technical_500_response(request, exc_type, exc_value, tb, status_code=500):
Create a technical server error response. The last three arguments are
the values returned from sys.exc_info() and friends.
"""
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb)
if request.is_ajax():
text = reporter.get_traceback_text()
return HttpResponse(text, status=status_code, content_type='text/plain; charset=utf-8')
@ -67,6 +67,11 @@ def get_exception_reporter_filter(request):
return getattr(request, 'exception_reporter_filter', default_filter)
def get_exception_reporter_class(request):
default_exception_reporter_class = import_string(settings.DEFAULT_EXCEPTION_REPORTER)
return getattr(request, 'exception_reporter_class', default_exception_reporter_class)
class SafeExceptionReporterFilter:
"""
Use annotations made by the sensitive_post_parameters and

View File

@ -305,6 +305,62 @@ following attributes and methods:
traceback frame. Sensitive values are replaced with
:attr:`cleansed_substitute`.
.. versionadded:: 3.1
If you need to customize error reports beyond filtering you may specify a
custom error reporter class by defining the
:setting:`DEFAULT_EXCEPTION_REPORTER` setting::
DEFAULT_EXCEPTION_REPORTER = 'path.to.your.CustomExceptionReporter'
The exception reporter is responsible for compiling the exception report data,
and formatting it as text or HTML appropriately. (The exception reporter uses
:setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` when preparing the exception
report data.)
Your custom reporter class needs to inherit from
:class:`django.views.debug.ExceptionReporter`.
.. class:: ExceptionReporter
.. method:: get_traceback_data()
Return a dictionary containing traceback information.
This is the main extension point for customizing exception reports, for
example::
from django.views.debug import ExceptionReporter
class CustomExceptionReporter(ExceptionReporter):
def get_traceback_data(self):
data = super().get_traceback_data()
# ... remove/add something here ...
return data
.. method:: get_traceback_html()
Return HTML version of exception report.
Used for HTML version of debug 500 HTTP error page.
.. method:: get_traceback_text()
Return plain text version of exception report.
Used for plain text version of debug 500 HTTP error page and email
reports.
As with the filter class, you may control which exception reporter class to use
within any given view by setting the ``HttpRequest``s
``exception_reporter_class`` attribute::
def my_view(request):
if request.user.is_authenticated:
request.exception_reporter_class = CustomExceptionReporter()
...
.. seealso::
You can also set up custom error reporting by writing a custom piece of

View File

@ -1250,6 +1250,19 @@ Default: ``'utf-8'``
Default charset to use for all ``HttpResponse`` objects, if a MIME type isn't
manually specified. Used when constructing the ``Content-Type`` header.
.. setting:: DEFAULT_EXCEPTION_REPORTER
``DEFAULT_EXCEPTION_REPORTER``
------------------------------
.. versionadded:: 3.1
Default: ``'``:class:`django.views.debug.ExceptionReporter`\ ``'``
Default exception reporter class to be used if none has been assigned to the
:class:`~django.http.HttpRequest` instance yet. See
:ref:`custom-error-reports`.
.. setting:: DEFAULT_EXCEPTION_REPORTER_FILTER
``DEFAULT_EXCEPTION_REPORTER_FILTER``
@ -3537,6 +3550,7 @@ Email
Error reporting
---------------
* :setting:`DEFAULT_EXCEPTION_REPORTER`
* :setting:`DEFAULT_EXCEPTION_REPORTER_FILTER`
* :setting:`IGNORABLE_404_URLS`
* :setting:`MANAGERS`

View File

@ -173,6 +173,10 @@ Error Reporting
:setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` when applying settings
filtering.
* The new :setting:`DEFAULT_EXCEPTION_REPORTER` allows providing a
:class:`django.views.debug.ExceptionReporter` subclass to customize exception
report generation. See :ref:`custom-error-reports` for details.
File Storage
~~~~~~~~~~~~

View File

@ -249,6 +249,15 @@ class DebugViewTests(SimpleTestCase):
response = self.client.get('/path-post/1/')
self.assertContains(response, 'Page not found', status_code=404)
def test_exception_reporter_from_request(self):
response = self.client.get('/custom_reporter_class_view/')
self.assertContains(response, 'custom traceback text', status_code=500)
@override_settings(DEFAULT_EXCEPTION_REPORTER='view_tests.views.CustomExceptionReporter')
def test_exception_reporter_from_settings(self):
response = self.client.get('/raises500/')
self.assertContains(response, 'custom traceback text', status_code=500)
class DebugViewQueriesAllowedTests(SimpleTestCase):
# May need a query to initialize MySQL connection

View File

@ -26,6 +26,7 @@ urlpatterns = [
path('raises403/', views.raises403),
path('raises404/', views.raises404),
path('raises500/', views.raises500),
path('custom_reporter_class_view/', views.custom_reporter_class_view),
path('technical404/', views.technical404, name='my404'),
path('classbased404/', views.Http404View.as_view()),

View File

@ -10,7 +10,7 @@ from django.template import TemplateDoesNotExist
from django.urls import get_resolver
from django.views import View
from django.views.debug import (
SafeExceptionReporterFilter, technical_500_response,
ExceptionReporter, SafeExceptionReporterFilter, technical_500_response,
)
from django.views.decorators.debug import (
sensitive_post_parameters, sensitive_variables,
@ -227,6 +227,22 @@ def custom_exception_reporter_filter_view(request):
return technical_500_response(request, *exc_info)
class CustomExceptionReporter(ExceptionReporter):
custom_traceback_text = 'custom traceback text'
def get_traceback_html(self):
return self.custom_traceback_text
def custom_reporter_class_view(request):
request.exception_reporter_class = CustomExceptionReporter
try:
raise Exception
except Exception:
exc_info = sys.exc_info()
return technical_500_response(request, *exc_info)
class Klass:
@sensitive_variables('sauce')