The solution is to prevent Django from configuring logging and handle it ourselves. Fortunately this is easy. In settings.py:
LOGGING_CONFIG = None
LOGGING = {...} # whatever you want, as you already have
import logging.config
logging.config.dictConfig(LOGGING)
UPDATE ~March 2015: Django has clarified their documentation:
If the disable_existing_loggers key in the LOGGING dictConfig is set
to True then all loggers from the default
configuration will be disabled. Disabled loggers are not the same as
removed; the logger will still exist, but will silently discard
anything logged to it, not even propagating entries to a parent
logger. Thus you should be very careful using
‘disable_existing_loggers’: True; it’s probably not what you want.
Instead, you can set disable_existing_loggers to False and redefine
some or all of the default loggers; or you can set LOGGING_CONFIG to
None and handle logging config yourself.
For posterity and detail: The explanation? Most of the confusion I think comes down to Django’s poor explanation of disable_existing_loggers, which says that when True, “the default configuration is completely overridden”. In your own answer you discovered that is not correct; what’s happening is that the existing loggers, which Django already configures, are disabled not replaced.
The Python logging documentation explains it better (emphasis added):
disable_existing_loggers – If specified as False, loggers which exist
when this call is made are left alone. The default is True because
this enables old behaviour in a backward-compatible way. This
behaviour is to disable any existing loggers unless they or their
ancestors are explicitly named in the logging configuration.
Based on Django docs we think, “override the defaults with my own LOGGING configuration and anything I don’t specify will bubble up“. I’ve tripped over this expectation as well. The behavior we expect is along the lines of replace_existing_loggers (which isn’t a real thing). Instead the Django loggers are shut up not bubbled up.
We need to prevent the setup of these Django loggers in the first place and here the Django docs are more helpful:
If you don’t want to configure logging at all (or you want to manually
configure logging using your own approach), you can set LOGGING_CONFIG
to None. This will disable the configuration process.Note: Setting LOGGING_CONFIG to None only means that the configuration
process is disabled, not logging itself. If you disable the
configuration process, Django will still make logging calls, falling
back to whatever default logging behavior is defined.
Django will still use its loggers but since they are not handled (and then disabled) by the configuration, those loggers will bubble up as expected. A simple test with the above settings:
manage.py shell
>>> import logging
>>> logging.warning('root logger')
WARNING 2014-03-11 13:35:08,832 root root logger
>>> l = logging.getLogger('django.request')
>>> l.warning('request logger')
WARNING 2014-03-11 13:38:22,000 django.request request logger
>>> l.propagate, l.disabled
(1, 0)