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. # Custom logging configuration.
LOGGING = {} 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 # Default exception reporter filter class used in case none has been
# specifically assigned to the HttpRequest instance. # specifically assigned to the HttpRequest instance.
DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFilter' DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFilter'

View File

@ -86,7 +86,7 @@ class AdminEmailHandler(logging.Handler):
super().__init__() super().__init__()
self.include_html = include_html self.include_html = include_html
self.email_backend = email_backend 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): def emit(self, record):
try: 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 Create a technical server error response. The last three arguments are
the values returned from sys.exc_info() and friends. 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(): if request.is_ajax():
text = reporter.get_traceback_text() text = reporter.get_traceback_text()
return HttpResponse(text, status=status_code, content_type='text/plain; charset=utf-8') 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) 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: class SafeExceptionReporterFilter:
""" """
Use annotations made by the sensitive_post_parameters and 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 traceback frame. Sensitive values are replaced with
:attr:`cleansed_substitute`. :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:: .. seealso::
You can also set up custom error reporting by writing a custom piece of 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 Default charset to use for all ``HttpResponse`` objects, if a MIME type isn't
manually specified. Used when constructing the ``Content-Type`` header. 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 .. setting:: DEFAULT_EXCEPTION_REPORTER_FILTER
``DEFAULT_EXCEPTION_REPORTER_FILTER`` ``DEFAULT_EXCEPTION_REPORTER_FILTER``
@ -3537,6 +3550,7 @@ Email
Error reporting Error reporting
--------------- ---------------
* :setting:`DEFAULT_EXCEPTION_REPORTER`
* :setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` * :setting:`DEFAULT_EXCEPTION_REPORTER_FILTER`
* :setting:`IGNORABLE_404_URLS` * :setting:`IGNORABLE_404_URLS`
* :setting:`MANAGERS` * :setting:`MANAGERS`

View File

@ -173,6 +173,10 @@ Error Reporting
:setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` when applying settings :setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` when applying settings
filtering. 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 File Storage
~~~~~~~~~~~~ ~~~~~~~~~~~~

View File

@ -249,6 +249,15 @@ class DebugViewTests(SimpleTestCase):
response = self.client.get('/path-post/1/') response = self.client.get('/path-post/1/')
self.assertContains(response, 'Page not found', status_code=404) 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): class DebugViewQueriesAllowedTests(SimpleTestCase):
# May need a query to initialize MySQL connection # May need a query to initialize MySQL connection

View File

@ -26,6 +26,7 @@ urlpatterns = [
path('raises403/', views.raises403), path('raises403/', views.raises403),
path('raises404/', views.raises404), path('raises404/', views.raises404),
path('raises500/', views.raises500), path('raises500/', views.raises500),
path('custom_reporter_class_view/', views.custom_reporter_class_view),
path('technical404/', views.technical404, name='my404'), path('technical404/', views.technical404, name='my404'),
path('classbased404/', views.Http404View.as_view()), 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.urls import get_resolver
from django.views import View from django.views import View
from django.views.debug import ( from django.views.debug import (
SafeExceptionReporterFilter, technical_500_response, ExceptionReporter, SafeExceptionReporterFilter, technical_500_response,
) )
from django.views.decorators.debug import ( from django.views.decorators.debug import (
sensitive_post_parameters, sensitive_variables, sensitive_post_parameters, sensitive_variables,
@ -227,6 +227,22 @@ def custom_exception_reporter_filter_view(request):
return technical_500_response(request, *exc_info) 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: class Klass:
@sensitive_variables('sauce') @sensitive_variables('sauce')