diff --git a/django/test/utils.py b/django/test/utils.py index 0101919d11..da77d8ef4e 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -616,3 +616,18 @@ def override_script_prefix(prefix): Decorator or context manager to temporary override the script prefix. """ return ScriptPrefix(prefix) + + +class LoggingCaptureMixin(object): + """ + Capture the output from the 'django' logger and store it on the class's + logger_output attribute. + """ + def setUp(self): + self.logger = logging.getLogger('django') + self.old_stream = self.logger.handlers[0].stream + self.logger_output = six.StringIO() + self.logger.handlers[0].stream = self.logger_output + + def tearDown(self): + self.logger.handlers[0].stream = self.old_stream diff --git a/django/utils/log.py b/django/utils/log.py index 7ccc532a05..22452f994c 100644 --- a/django/utils/log.py +++ b/django/utils/log.py @@ -40,17 +40,7 @@ DEFAULT_LOGGING = { }, 'loggers': { 'django': { - 'handlers': ['console'], - }, - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': False, - }, - 'django.security': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': False, + 'handlers': ['console', 'mail_admins'], }, 'py.warnings': { 'handlers': ['console'], diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 8f135d2376..c73d5bf209 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -358,6 +358,26 @@ template object. These classes have been combined into :class:`~django.template.base.Origin` and is now always set regardless of the engine debug setting. +.. _default-logging-changes-19: + +Changes to the default logging configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To make it easier to write custom logging configurations, Django's default +logging configuration no longer defines 'django.request' and 'django.security' +loggers. Instead, it defines a single 'django' logger with two handlers: + +* 'console': filtered at the ``INFO`` level and only active if ``DEBUG=True``. +* 'mail_admins': filtered at the ``ERROR`` level and only active if + ``DEBUG=False``. + +If you aren't overriding Django's default logging, you should see minimal +changes in behavior, but you might see some new logging to the ``runserver`` +console, for example. + +If you are overriding Django's default logging, you should check to see how +your configuration merges with the new defaults. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 89de6d56a4..83283b709d 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -266,12 +266,12 @@ If you use this example, be sure to change the ``'filename'`` path to a location that's writable by the user that's running the Django application. Second, here's an example of how to make the logging system print Django's -logging to the console. It overrides the fact that ``django.request`` and -``django.security`` don't propagate their log entries by default. It may be -useful during local development. +logging to the console. It may be useful during local development. By default, this config only sends messages of level ``INFO`` or higher to the -console. Django does not log many such messages. Set the environment variable +console (same as Django's default logging config, except that the default only +displays log records when ``DEBUG=True``). Django does not log many such +messages. With this config, however, you can also set the environment variable ``DJANGO_LOG_LEVEL=DEBUG`` to see all of Django's debug logging which is very verbose as it includes all database queries:: @@ -293,6 +293,11 @@ verbose as it includes all database queries:: }, } +.. versionchanged:: 1.9 + + Django's default logging configuration changed. See :ref:`the release notes + ` for a description of the changes. + Finally, here's an example of a fairly complex logging setup:: LOGGING = { @@ -525,14 +530,12 @@ a client that does not match :setting:`ALLOWED_HOSTS`, Django will return a 400 response, and an error message will be logged to the ``django.security.DisallowedHost`` logger. -Only the parent ``django.security`` logger is configured by default, and all -child loggers will propagate to the parent logger. The ``django.security`` -logger is configured the same as the ``django.request`` logger, and any error -events will be mailed to admins. Requests resulting in a 400 response due to -a ``SuspiciousOperation`` will not be logged to the ``django.request`` logger, -but only to the ``django.security`` logger. +These log events will reach the 'django' logger by default, which mails error +events to admins when ``DEBUG=False``. Requests resulting in a 400 response due +to a ``SuspiciousOperation`` will not be logged to the ``django.request`` +logger, but only to the ``django.security`` logger. -To silence a particular type of SuspiciousOperation, you can override that +To silence a particular type of ``SuspiciousOperation``, you can override that specific logger following this example: .. code-block:: python @@ -704,20 +707,20 @@ By default, Django configures the following logging: When :setting:`DEBUG` is ``True``: * The ``django`` catch-all logger sends all messages at the ``INFO`` level or - higher to the console. Django doesn't make any such logging calls at this - time (all logging is at the ``DEBUG`` level or handled by the - ``django.request`` and ``django.security`` loggers). + higher to the console. * The ``py.warnings`` logger, which handles messages from ``warnings.warn()``, sends messages to the console. When :setting:`DEBUG` is ``False``: -* The ``django.request`` and ``django.security`` loggers send messages with - ``ERROR`` or ``CRITICAL`` level to :class:`AdminEmailHandler`. These loggers - ignore anything at the ``WARNING`` level or below and log entries aren't - propagated to other loggers (they won't reach the ``django`` catch-all - logger, even when ``DEBUG`` is ``True``). +* The ``django`` logger send messages with ``ERROR`` or ``CRITICAL`` level to + :class:`AdminEmailHandler`. + +.. versionchanged:: 1.9 + + Django's default logging configuration changed. See :ref:`the release notes + ` for a description of the changes. See also :ref:`Configuring logging ` to learn how you can complement or replace this default logging configuration. diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py index a349e4f35b..5ee1092c41 100644 --- a/tests/logging_tests/tests.py +++ b/tests/logging_tests/tests.py @@ -9,7 +9,7 @@ from admin_scripts.tests import AdminScriptTestCase from django.core import mail from django.core.files.temp import NamedTemporaryFile from django.test import RequestFactory, TestCase, override_settings -from django.test.utils import patch_logger +from django.test.utils import LoggingCaptureMixin, patch_logger from django.utils.deprecation import RemovedInNextVersionWarning from django.utils.encoding import force_text from django.utils.log import ( @@ -65,26 +65,18 @@ class LoggingFiltersTest(TestCase): self.assertEqual(filter_.filter("record is not used"), False) -class DefaultLoggingTest(TestCase): - def setUp(self): - self.logger = logging.getLogger('django') - self.old_stream = self.logger.handlers[0].stream - - def tearDown(self): - self.logger.handlers[0].stream = self.old_stream +class DefaultLoggingTest(LoggingCaptureMixin, TestCase): def test_django_logger(self): """ The 'django' base logger only output anything when DEBUG=True. """ - output = StringIO() - self.logger.handlers[0].stream = output self.logger.error("Hey, this is an error.") - self.assertEqual(output.getvalue(), '') + self.assertEqual(self.logger_output.getvalue(), '') with self.settings(DEBUG=True): self.logger.error("Hey, this is an error.") - self.assertEqual(output.getvalue(), 'Hey, this is an error.\n') + self.assertEqual(self.logger_output.getvalue(), 'Hey, this is an error.\n') class WarningLoggerTests(TestCase): @@ -167,11 +159,10 @@ class CallbackFilterTest(TestCase): class AdminEmailHandlerTest(TestCase): - logger = logging.getLogger('django.request') + logger = logging.getLogger('django') def get_admin_email_handler(self, logger): - # Inspired from views/views.py: send_log() - # ensuring the AdminEmailHandler does not get filtered out + # Ensure that AdminEmailHandler does not get filtered out # even with DEBUG=True. admin_email_handler = [ h for h in logger.handlers diff --git a/tests/runtests.py b/tests/runtests.py index cc814a6d2e..7a2143e15d 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -19,6 +19,7 @@ from django.utils._os import upath from django.utils.deprecation import ( RemovedInDjango20Warning, RemovedInDjango21Warning, ) +from django.utils.log import DEFAULT_LOGGING warnings.simplefilter("error", RemovedInDjango20Warning) warnings.simplefilter("error", RemovedInDjango21Warning) @@ -144,6 +145,11 @@ def setup(verbosity, test_labels): 'auth': 'django.contrib.auth.tests.migrations', 'contenttypes': 'contenttypes_tests.migrations', } + log_config = DEFAULT_LOGGING + # Filter out non-error logging so we don't have to capture it in lots of + # tests. + log_config['loggers']['django']['level'] = 'ERROR' + settings.LOGGING = log_config if verbosity > 0: # Ensure any warnings captured to logging are piped through a verbose diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index 9dca0fb339..ca852f0f0d 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -16,6 +16,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.core.urlresolvers import reverse from django.template.base import TemplateDoesNotExist from django.test import RequestFactory, TestCase, override_settings +from django.test.utils import LoggingCaptureMixin from django.utils import six from django.utils.encoding import force_bytes, force_text from django.utils.functional import SimpleLazyObject @@ -48,7 +49,7 @@ class CallableSettingWrapperTests(TestCase): @override_settings(DEBUG=True, ROOT_URLCONF="view_tests.urls") -class DebugViewTests(TestCase): +class DebugViewTests(LoggingCaptureMixin, TestCase): def test_files(self): response = self.client.get('/raises/') @@ -642,7 +643,7 @@ class ExceptionReportTestMixin(object): @override_settings(ROOT_URLCONF='view_tests.urls') -class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): +class ExceptionReporterFilterTests(ExceptionReportTestMixin, LoggingCaptureMixin, TestCase): """ Ensure that sensitive information can be filtered out of error reports. Refs #14614. @@ -833,7 +834,7 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): self.assertNotContains(response, 'should not be displayed', status_code=500) -class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin): +class AjaxResponseExceptionReporterFilter(ExceptionReportTestMixin, LoggingCaptureMixin, TestCase): """ Ensure that sensitive information can be filtered out of error reports. diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py index c5ea86c101..c4d025c349 100644 --- a/tests/view_tests/views.py +++ b/tests/view_tests/views.py @@ -102,7 +102,7 @@ def render_no_template(request): def send_log(request, exc_info): - logger = logging.getLogger('django.request') + logger = logging.getLogger('django') # The default logging config has a logging filter to ensure admin emails are # only sent with DEBUG=False, but since someone might choose to remove that # filter, we still want to be able to test the behavior of error emails