Commit fc69fff9 authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #14861 -- Moved logging config outside of Settings.__init__

Thanks donspaulding for the report and simonpercivall for the
initial patch.
parent e72e22e5
Loading
Loading
Loading
Loading
+16 −14
Original line number Diff line number Diff line
@@ -43,13 +43,28 @@ class LazySettings(LazyObject):
                % (name, ENVIRONMENT_VARIABLE))

        self._wrapped = Settings(settings_module)

        self._configure_logging()

    def __getattr__(self, name):
        if self._wrapped is empty:
            self._setup(name)
        return getattr(self._wrapped, name)

    def _configure_logging(self):
        """
        Setup logging from LOGGING_CONFIG and LOGGING settings.
        """
        if self.LOGGING_CONFIG:
            # First find the logging configuration function ...
            logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
            logging_config_module = importlib.import_module(logging_config_path)
            logging_config_func = getattr(logging_config_module, logging_config_func_name)

            # Backwards-compatibility shim for #16288 fix
            compat_patch_logging_config(self.LOGGING)

            # ... then invoke it with the logging settings
            logging_config_func(self.LOGGING)

    def configure(self, default_settings=global_settings, **options):
        """
@@ -133,19 +148,6 @@ class Settings(BaseSettings):
            os.environ['TZ'] = self.TIME_ZONE
            time.tzset()

        # Settings are configured, so we can set up the logger if required
        if self.LOGGING_CONFIG:
            # First find the logging configuration function ...
            logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
            logging_config_module = importlib.import_module(logging_config_path)
            logging_config_func = getattr(logging_config_module, logging_config_func_name)

            # Backwards-compatibility shim for #16288 fix
            compat_patch_logging_config(self.LOGGING)

            # ... then invoke it with the logging settings
            logging_config_func(self.LOGGING)


class UserSettingsHolder(BaseSettings):
    """
+0 −30
Original line number Diff line number Diff line
@@ -345,36 +345,6 @@ This logging configuration does the following things:
    printed to the console; ``ERROR`` and ``CRITICAL``
    messages will also be output via email.

.. admonition:: Custom handlers and circular imports

    If your ``settings.py`` specifies a custom handler class and the file
    defining that class also imports ``settings.py`` a circular import will
    occur.

    For example, if ``settings.py`` contains the following config for
    :setting:`LOGGING`::

        LOGGING = {
          'version': 1,
          'handlers': {
            'custom_handler': {
              'level': 'INFO',
              'class': 'myproject.logconfig.MyHandler',
            }
          }
        }

    and ``myproject/logconfig.py`` has the following line before the
    ``MyHandler`` definition::

        from django.conf import settings

    then the ``dictconfig`` module will raise an exception like the following::

        ValueError: Unable to configure handler 'custom_handler':
        Unable to configure handler 'custom_handler':
        'module' object has no attribute 'logconfig'

.. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects

Custom logging configuration
+7 −0
Original line number Diff line number Diff line
import logging

from django.conf import settings

class MyHandler(logging.Handler):
    def __init__(self, *args, **kwargs):
        self.config = settings.LOGGING
+29 −0
Original line number Diff line number Diff line
@@ -10,6 +10,8 @@ from django.test import TestCase, RequestFactory
from django.test.utils import override_settings
from django.utils.log import CallbackFilter, RequireDebugFalse

from ..admin_scripts.tests import AdminScriptTestCase


# logging config prior to using filter with mail_admins
OLD_LOGGING = {
@@ -253,3 +255,30 @@ class AdminEmailHandlerTest(TestCase):

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, expected_subject)


class SettingsConfigTest(AdminScriptTestCase):
    """
    Test that accessing settings in a custom logging handler does not trigger
    a circular import error.
    """
    def setUp(self):
        log_config = """{
    'version': 1,
    'handlers': {
        'custom_handler': {
            'level': 'INFO',
            'class': 'logging_tests.logconfig.MyHandler',
        }
    }
}"""
        self.write_settings('settings.py', sdict={'LOGGING': log_config})

    def tearDown(self):
        self.remove_settings('settings.py')

    def test_circular_dependency(self):
        # validate is just an example command to trigger settings configuration
        out, err = self.run_manage(['validate'])
        self.assertNoOutput(err)
        self.assertOutput(out, "0 errors found")