diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index f4d17ca..2fb2963 100644
a
|
b
|
class LazySettings(LazyObject):
|
43 | 43 | |
44 | 44 | self._wrapped = Settings(settings_module) |
45 | 45 | |
| 46 | # Settings are configured, so we can set up the logger if required |
| 47 | if self._wrapped.LOGGING_CONFIG: |
| 48 | # First find the logging configuration function ... |
| 49 | logging_config_path, logging_config_func_name = self._wrapped.LOGGING_CONFIG.rsplit('.', 1) |
| 50 | logging_config_module = importlib.import_module(logging_config_path) |
| 51 | logging_config_func = getattr(logging_config_module, logging_config_func_name) |
| 52 | |
| 53 | # Backwards-compatibility shim for #16288 fix |
| 54 | compat_patch_logging_config(self._wrapped.LOGGING) |
| 55 | |
| 56 | # ... then invoke it with the logging settings |
| 57 | logging_config_func(self._wrapped.LOGGING) |
| 58 | |
46 | 59 | def configure(self, default_settings=global_settings, **options): |
47 | 60 | """ |
48 | 61 | Called to manually configure the settings. The 'default_settings' |
… |
… |
class Settings(BaseSettings):
|
125 | 138 | os.environ['TZ'] = self.TIME_ZONE |
126 | 139 | time.tzset() |
127 | 140 | |
128 | | # Settings are configured, so we can set up the logger if required |
129 | | if self.LOGGING_CONFIG: |
130 | | # First find the logging configuration function ... |
131 | | logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1) |
132 | | logging_config_module = importlib.import_module(logging_config_path) |
133 | | logging_config_func = getattr(logging_config_module, logging_config_func_name) |
134 | | |
135 | | # Backwards-compatibility shim for #16288 fix |
136 | | compat_patch_logging_config(self.LOGGING) |
137 | | |
138 | | # ... then invoke it with the logging settings |
139 | | logging_config_func(self.LOGGING) |
140 | | |
141 | 141 | |
142 | 142 | class UserSettingsHolder(BaseSettings): |
143 | 143 | """ |
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
index b54a947..d7e97ea 100644
a
|
b
|
This logging configuration does the following things:
|
347 | 347 | printed to the console; ``ERROR`` and ``CRITICAL`` |
348 | 348 | messages will also be output via email. |
349 | 349 | |
350 | | .. admonition:: Custom handlers and circular imports |
351 | | |
352 | | If your ``settings.py`` specifies a custom handler class and the file |
353 | | defining that class also imports ``settings.py`` a circular import will |
354 | | occur. |
355 | | |
356 | | For example, if ``settings.py`` contains the following config for |
357 | | :setting:`LOGGING`:: |
358 | | |
359 | | LOGGING = { |
360 | | 'version': 1, |
361 | | 'handlers': { |
362 | | 'custom_handler': { |
363 | | 'level': 'INFO', |
364 | | 'class': 'myproject.logconfig.MyHandler', |
365 | | } |
366 | | } |
367 | | } |
368 | | |
369 | | and ``myproject/logconfig.py`` has the following line before the |
370 | | ``MyHandler`` definition:: |
371 | | |
372 | | from django.conf import settings |
373 | | |
374 | | then the ``dictconfig`` module will raise an exception like the following:: |
375 | | |
376 | | ValueError: Unable to configure handler 'custom_handler': |
377 | | Unable to configure handler 'custom_handler': |
378 | | 'module' object has no attribute 'logconfig' |
379 | | |
380 | | .. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects |
381 | | |
382 | 350 | Custom logging configuration |
383 | 351 | ---------------------------- |
384 | 352 | |
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 4ae3c1b..fe65fca 100644
a
|
b
|
from django.test import TestCase, RequestFactory
|
9 | 9 | from django.test.utils import override_settings |
10 | 10 | from django.utils.log import CallbackFilter, RequireDebugFalse, getLogger |
11 | 11 | |
| 12 | from ..admin_scripts.tests import AdminScriptTestCase |
| 13 | |
12 | 14 | |
13 | 15 | # logging config prior to using filter with mail_admins |
14 | 16 | OLD_LOGGING = { |
… |
… |
class AdminEmailHandlerTest(TestCase):
|
252 | 254 | |
253 | 255 | self.assertEqual(len(mail.outbox), 1) |
254 | 256 | self.assertEqual(mail.outbox[0].subject, expected_subject) |
| 257 | |
| 258 | |
| 259 | class SettingsConfigTest(AdminScriptTestCase): |
| 260 | """ |
| 261 | Test that accessing settings in a custom logging handler does not trigger |
| 262 | a circular import error. |
| 263 | """ |
| 264 | def setUp(self): |
| 265 | log_config = """{ |
| 266 | 'version': 1, |
| 267 | 'handlers': { |
| 268 | 'custom_handler': { |
| 269 | 'level': 'INFO', |
| 270 | 'class': 'logging_tests.logconfig.MyHandler', |
| 271 | } |
| 272 | } |
| 273 | }""" |
| 274 | self.write_settings('settings.py', sdict={'LOGGING': log_config}) |
| 275 | |
| 276 | def tearDown(self): |
| 277 | self.remove_settings('settings.py') |
| 278 | |
| 279 | def test_circular_dependency(self): |
| 280 | # validate is just an example command to trigger settings configuration |
| 281 | out, err = self.run_manage(['validate']) |
| 282 | self.assertNoOutput(err) |
| 283 | self.assertOutput(out, "0 errors found") |