Fixed #14861 -- Moved logging config outside of Settings.__init__
Thanks donspaulding for the report and simonpercivall for the initial patch.
This commit is contained in:
parent
e72e22e518
commit
fc69fff9ab
|
@ -43,13 +43,28 @@ class LazySettings(LazyObject):
|
||||||
% (name, ENVIRONMENT_VARIABLE))
|
% (name, ENVIRONMENT_VARIABLE))
|
||||||
|
|
||||||
self._wrapped = Settings(settings_module)
|
self._wrapped = Settings(settings_module)
|
||||||
|
self._configure_logging()
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if self._wrapped is empty:
|
if self._wrapped is empty:
|
||||||
self._setup(name)
|
self._setup(name)
|
||||||
return getattr(self._wrapped, 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):
|
def configure(self, default_settings=global_settings, **options):
|
||||||
"""
|
"""
|
||||||
|
@ -133,19 +148,6 @@ class Settings(BaseSettings):
|
||||||
os.environ['TZ'] = self.TIME_ZONE
|
os.environ['TZ'] = self.TIME_ZONE
|
||||||
time.tzset()
|
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):
|
class UserSettingsHolder(BaseSettings):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -345,36 +345,6 @@ This logging configuration does the following things:
|
||||||
printed to the console; ``ERROR`` and ``CRITICAL``
|
printed to the console; ``ERROR`` and ``CRITICAL``
|
||||||
messages will also be output via email.
|
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
|
.. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects
|
||||||
|
|
||||||
Custom logging configuration
|
Custom logging configuration
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
class MyHandler(logging.Handler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.config = settings.LOGGING
|
|
@ -10,6 +10,8 @@ from django.test import TestCase, RequestFactory
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
from django.utils.log import CallbackFilter, RequireDebugFalse
|
from django.utils.log import CallbackFilter, RequireDebugFalse
|
||||||
|
|
||||||
|
from ..admin_scripts.tests import AdminScriptTestCase
|
||||||
|
|
||||||
|
|
||||||
# logging config prior to using filter with mail_admins
|
# logging config prior to using filter with mail_admins
|
||||||
OLD_LOGGING = {
|
OLD_LOGGING = {
|
||||||
|
@ -253,3 +255,30 @@ class AdminEmailHandlerTest(TestCase):
|
||||||
|
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].subject, expected_subject)
|
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…
Reference in New Issue