Browse Source

Fixed #29714 -- Allowed using ExceptionReporter subclass with AdminEmailHandler.

Nasir Hussain 6 years ago
parent
commit
25706d7285

+ 3 - 3
django/utils/log.py

@@ -7,7 +7,6 @@ from django.core import mail
 from django.core.mail import get_connection
 from django.core.mail import get_connection
 from django.core.management.color import color_style
 from django.core.management.color import color_style
 from django.utils.module_loading import import_string
 from django.utils.module_loading import import_string
-from django.views.debug import ExceptionReporter
 
 
 request_logger = logging.getLogger('django.request')
 request_logger = logging.getLogger('django.request')
 
 
@@ -83,10 +82,11 @@ class AdminEmailHandler(logging.Handler):
     request data will be provided in the email report.
     request data will be provided in the email report.
     """
     """
 
 
-    def __init__(self, include_html=False, email_backend=None):
+    def __init__(self, include_html=False, email_backend=None, reporter_class=None):
         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')
 
 
     def emit(self, record):
     def emit(self, record):
         try:
         try:
@@ -116,7 +116,7 @@ class AdminEmailHandler(logging.Handler):
         else:
         else:
             exc_info = (None, record.getMessage(), None)
             exc_info = (None, record.getMessage(), None)
 
 
-        reporter = ExceptionReporter(request, is_email=True, *exc_info)
+        reporter = self.reporter_class(request, is_email=True, *exc_info)
         message = "%s\n\n%s" % (self.format(no_exc_record), reporter.get_traceback_text())
         message = "%s\n\n%s" % (self.format(no_exc_record), reporter.get_traceback_text())
         html_message = reporter.get_traceback_html() if self.include_html else None
         html_message = reporter.get_traceback_html() if self.include_html else None
         self.send_mail(subject, message, fail_silently=True, html_message=html_message)
         self.send_mail(subject, message, fail_silently=True, html_message=html_message)

+ 8 - 0
docs/releases/3.0.txt

@@ -259,6 +259,14 @@ Internationalization
   language cookies. The default values of these settings preserve the previous
   language cookies. The default values of these settings preserve the previous
   behavior.
   behavior.
 
 
+Logging
+~~~~~~~
+
+* The new ``reporter_class`` parameter of
+  :class:`~django.utils.log.AdminEmailHandler` allows providing an
+  ``django.views.debug.ExceptionReporter`` subclass to customize the traceback
+  text sent to site :setting:`ADMINS` when :setting:`DEBUG` is ``False``.
+
 Management Commands
 Management Commands
 ~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~
 
 

+ 21 - 1
docs/topics/logging.txt

@@ -595,7 +595,7 @@ Handlers
 Django provides one log handler in addition to those provided by the
 Django provides one log handler in addition to those provided by the
 Python logging module.
 Python logging module.
 
 
-.. class:: AdminEmailHandler(include_html=False, email_backend=None)
+.. class:: AdminEmailHandler(include_html=False, email_backend=None, reporter_class=None)
 
 
     This handler sends an email to the site :setting:`ADMINS` for each log
     This handler sends an email to the site :setting:`ADMINS` for each log
     message it receives.
     message it receives.
@@ -652,6 +652,26 @@ Python logging module.
     By default, an instance of the email backend specified in
     By default, an instance of the email backend specified in
     :setting:`EMAIL_BACKEND` will be used.
     :setting:`EMAIL_BACKEND` will be used.
 
 
+    The ``reporter_class`` argument of ``AdminEmailHandler`` allows providing
+    an ``django.views.debug.ExceptionReporter`` subclass to customize the
+    traceback text sent in the email body. You provide a string import path to
+    the class you wish to use, like this:
+
+    .. code-block:: python
+
+        'handlers': {
+            'mail_admins': {
+                'level': 'ERROR',
+                'class': 'django.utils.log.AdminEmailHandler',
+                'include_html': True,
+                'reporter_class': 'somepackage.error_reporter.CustomErrorReporter'
+            }
+        },
+
+    .. versionadded:: 3.0
+
+        The ``reporter_class`` argument was added.
+
     .. method:: send_mail(subject, message, *args, **kwargs)
     .. method:: send_mail(subject, message, *args, **kwargs)
 
 
         Sends emails to admin users. To customize this behavior, you can
         Sends emails to admin users. To customize this behavior, you can

+ 6 - 0
tests/logging_tests/logconfig.py

@@ -2,6 +2,7 @@ import logging
 
 
 from django.conf import settings
 from django.conf import settings
 from django.core.mail.backends.base import BaseEmailBackend
 from django.core.mail.backends.base import BaseEmailBackend
+from django.views.debug import ExceptionReporter
 
 
 
 
 class MyHandler(logging.Handler):
 class MyHandler(logging.Handler):
@@ -13,3 +14,8 @@ class MyHandler(logging.Handler):
 class MyEmailBackend(BaseEmailBackend):
 class MyEmailBackend(BaseEmailBackend):
     def send_messages(self, email_messages):
     def send_messages(self, email_messages):
         pass
         pass
+
+
+class CustomExceptionReporter(ExceptionReporter):
+    def get_traceback_text(self):
+        return 'custom traceback text'

+ 15 - 0
tests/logging_tests/tests.py

@@ -16,6 +16,7 @@ from django.utils.log import (
     DEFAULT_LOGGING, AdminEmailHandler, CallbackFilter, RequireDebugFalse,
     DEFAULT_LOGGING, AdminEmailHandler, CallbackFilter, RequireDebugFalse,
     RequireDebugTrue, ServerFormatter,
     RequireDebugTrue, ServerFormatter,
 )
 )
+from django.views.debug import ExceptionReporter
 
 
 from . import views
 from . import views
 from .logconfig import MyEmailBackend
 from .logconfig import MyEmailBackend
@@ -431,6 +432,20 @@ class AdminEmailHandlerTest(SimpleTestCase):
         finally:
         finally:
             admin_email_handler.include_html = old_include_html
             admin_email_handler.include_html = old_include_html
 
 
+    def test_default_exception_reporter_class(self):
+        admin_email_handler = self.get_admin_email_handler(self.logger)
+        self.assertEqual(admin_email_handler.reporter_class, ExceptionReporter)
+
+    @override_settings(ADMINS=[('A.N.Admin', 'admin@example.com')])
+    def test_custom_exception_reporter_is_used(self):
+        record = self.logger.makeRecord('name', logging.ERROR, 'function', 'lno', 'message', None, None)
+        record.request = self.request_factory.get('/')
+        handler = AdminEmailHandler(reporter_class='logging_tests.logconfig.CustomExceptionReporter')
+        handler.emit(record)
+        self.assertEqual(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEqual(msg.body, 'message\n\ncustom traceback text')
+
 
 
 class SettingsConfigTest(AdminScriptTestCase):
 class SettingsConfigTest(AdminScriptTestCase):
     """
     """