diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index 6272f4e..d636ff0 100644
a
|
b
|
class LazySettings(LazyObject):
|
43 | 43 | % (name, ENVIRONMENT_VARIABLE)) |
44 | 44 | |
45 | 45 | self._wrapped = Settings(settings_module) |
46 | | |
| 46 | self._configure_logging() |
47 | 47 | |
48 | 48 | def __getattr__(self, name): |
49 | 49 | if self._wrapped is empty: |
50 | 50 | self._setup(name) |
51 | 51 | return getattr(self._wrapped, name) |
52 | 52 | |
| 53 | def _configure_logging(self): |
| 54 | """ |
| 55 | Setup logging from LOGGING_CONFIG and LOGGING settings. |
| 56 | """ |
| 57 | if self.LOGGING_CONFIG: |
| 58 | # First find the logging configuration function ... |
| 59 | logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1) |
| 60 | logging_config_module = importlib.import_module(logging_config_path) |
| 61 | logging_config_func = getattr(logging_config_module, logging_config_func_name) |
| 62 | |
| 63 | # Backwards-compatibility shim for #16288 fix |
| 64 | compat_patch_logging_config(self.LOGGING) |
| 65 | |
| 66 | # ... then invoke it with the logging settings |
| 67 | logging_config_func(self.LOGGING) |
53 | 68 | |
54 | 69 | def configure(self, default_settings=global_settings, **options): |
55 | 70 | """ |
… |
… |
class Settings(BaseSettings):
|
133 | 148 | os.environ['TZ'] = self.TIME_ZONE |
134 | 149 | time.tzset() |
135 | 150 | |
136 | | # Settings are configured, so we can set up the logger if required |
137 | | if self.LOGGING_CONFIG: |
138 | | # First find the logging configuration function ... |
139 | | logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1) |
140 | | logging_config_module = importlib.import_module(logging_config_path) |
141 | | logging_config_func = getattr(logging_config_module, logging_config_func_name) |
142 | | |
143 | | # Backwards-compatibility shim for #16288 fix |
144 | | compat_patch_logging_config(self.LOGGING) |
145 | | |
146 | | # ... then invoke it with the logging settings |
147 | | logging_config_func(self.LOGGING) |
148 | | |
149 | 151 | |
150 | 152 | class UserSettingsHolder(BaseSettings): |
151 | 153 | """ |
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
index 94236ba..5fc229e 100644
a
|
b
|
This logging configuration does the following things:
|
347 | 347 | |
348 | 348 | .. admonition:: Custom handlers and circular imports |
349 | 349 | |
350 | | If your ``settings.py`` specifies a custom handler class and the file |
351 | | defining that class also imports ``settings.py`` a circular import will |
352 | | occur. |
353 | | |
354 | | For example, if ``settings.py`` contains the following config for |
355 | | :setting:`LOGGING`:: |
356 | | |
357 | | LOGGING = { |
358 | | 'version': 1, |
359 | | 'handlers': { |
360 | | 'custom_handler': { |
361 | | 'level': 'INFO', |
362 | | 'class': 'myproject.logconfig.MyHandler', |
363 | | } |
364 | | } |
365 | | } |
366 | | |
367 | | and ``myproject/logconfig.py`` has the following line before the |
368 | | ``MyHandler`` definition:: |
369 | | |
370 | | from django.conf import settings |
371 | | |
372 | | then the ``dictconfig`` module will raise an exception like the following:: |
373 | | |
374 | | ValueError: Unable to configure handler 'custom_handler': |
375 | | Unable to configure handler 'custom_handler': |
376 | | 'module' object has no attribute 'logconfig' |
| 350 | Logging configuration happens soon in the Django setup machinery. If your |
| 351 | ``settings.py`` specifies a custom handler class, you should be careful |
| 352 | about the modules you import in the file defining this class, unless a |
| 353 | circular import may occur. |
377 | 354 | |
378 | 355 | .. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects |
379 | 356 | |
diff --git a/tests/regressiontests/logging_tests/logconfig.py b/tests/regressiontests/logging_tests/logconfig.py
new file mode 100644
index 0000000..8524aa2
-
|
+
|
|
| 1 | import logging |
| 2 | |
| 3 | from django.conf import settings |
| 4 | |
| 5 | class MyHandler(logging.Handler): |
| 6 | def __init__(self, *args, **kwargs): |
| 7 | self.config = settings.LOGGING |
diff --git a/tests/regressiontests/logging_tests/tests.py b/tests/regressiontests/logging_tests/tests.py
index f444e0f..a54b425 100644
a
|
b
|
from django.test import TestCase, RequestFactory
|
10 | 10 | from django.test.utils import override_settings |
11 | 11 | from django.utils.log import CallbackFilter, RequireDebugFalse |
12 | 12 | |
| 13 | from ..admin_scripts.tests import AdminScriptTestCase |
| 14 | |
13 | 15 | |
14 | 16 | # logging config prior to using filter with mail_admins |
15 | 17 | OLD_LOGGING = { |
… |
… |
class AdminEmailHandlerTest(TestCase):
|
253 | 255 | |
254 | 256 | self.assertEqual(len(mail.outbox), 1) |
255 | 257 | self.assertEqual(mail.outbox[0].subject, expected_subject) |
| 258 | |
| 259 | |
| 260 | class SettingsConfigTest(AdminScriptTestCase): |
| 261 | """ |
| 262 | Test that accessing settings in a custom logging handler does not trigger |
| 263 | a circular import error. |
| 264 | """ |
| 265 | def setUp(self): |
| 266 | log_config = """{ |
| 267 | 'version': 1, |
| 268 | 'handlers': { |
| 269 | 'custom_handler': { |
| 270 | 'level': 'INFO', |
| 271 | 'class': 'logging_tests.logconfig.MyHandler', |
| 272 | } |
| 273 | } |
| 274 | }""" |
| 275 | self.write_settings('settings.py', sdict={'LOGGING': log_config}) |
| 276 | |
| 277 | def tearDown(self): |
| 278 | self.remove_settings('settings.py') |
| 279 | |
| 280 | def test_circular_dependency(self): |
| 281 | # validate is just an example command to trigger settings configuration |
| 282 | out, err = self.run_manage(['validate']) |
| 283 | self.assertNoOutput(err) |
| 284 | self.assertOutput(out, "0 errors found") |