Loading django/conf/__init__.py +16 −14 Original line number Diff line number Diff line Loading @@ -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): """ Loading Loading @@ -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): """ Loading docs/topics/logging.txt +0 −30 Original line number Diff line number Diff line Loading @@ -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 Loading tests/regressiontests/logging_tests/logconfig.py 0 → 100644 +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 tests/regressiontests/logging_tests/tests.py +29 −0 Original line number Diff line number Diff line Loading @@ -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 = { Loading Loading @@ -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") Loading
django/conf/__init__.py +16 −14 Original line number Diff line number Diff line Loading @@ -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): """ Loading Loading @@ -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): """ Loading
docs/topics/logging.txt +0 −30 Original line number Diff line number Diff line Loading @@ -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 Loading
tests/regressiontests/logging_tests/logconfig.py 0 → 100644 +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
tests/regressiontests/logging_tests/tests.py +29 −0 Original line number Diff line number Diff line Loading @@ -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 = { Loading Loading @@ -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")