Without the basicConfig()
line:
- The root logger has default logging level
WARNING
and has no handlers.
- The two loggers that you create don’t have handlers and their
propagate
attribute is True
.
Following the flowchart above, each LogRecord bubbles up to the root logger. The module_logger
only lets records of levels ERROR
and above to pass through. The logger
in main passes all LogRecord from INFO
and above to the upstairs.
The root logger doesn’t have handlers, but there is a “handler of last resort” in logging
that has level WARNING
.
>>> logging.lastResort
<_StderrHandler <stderr> (WARNING)>
So, all the records that got to root of level WARNING
and above get printed.
You can see their insides here
>>> import logging
>>> module_logger = logging.getLogger('spam_application.auxiliary')
>>> module_logger.setLevel(logging.ERROR)
>>> module_logger.__dict__
{'filters': [], 'name': 'spam_application.auxiliary', 'level': 40, 'parent': <RootLogger root (WARNING)>, 'propagate': True, 'handlers': [], 'disabled': False, '_cache': {}, 'manager': <logging.Manager object at 0x7f933e210400>}
>>> module_logger.parent.__dict__
{'filters': [], 'name': 'root', 'level': 30, 'parent': None, 'propagate': True, 'handlers': [], 'disabled': False, '_cache': {}}
>>> logger = logging.getLogger('__main__')
>>> logger.setLevel(logging.INFO)
>>> logger.__dict__
{'filters': [], 'name': '__main__', 'level': 20, 'parent': <RootLogger root (WARNING)>, 'propagate': True, 'handlers': [], 'disabled': False, '_cache': {}, 'manager': <logging.Manager object at 0x7f933e210400>}
With the basicConfig()
line: The root logger gets a handler StreamHandler
to stderr
. This handler has level NOTSET
. The loggers module_logger
and logger
(from main) are as above. The same LogRecords as before are created and bubbled up to the root logger. However, this one has now a handler and this handler (with its NOTSET
level) puts all messages that it receives to stderr
.
>>> import logging
>>> module_logger = logging.getLogger('spam_application.auxiliary')
>>> module_logger.setLevel(logging.ERROR)
>>> logging.basicConfig()
>>> module_logger.__dict__
{'filters': [], 'name': 'spam_application.auxiliary', 'level': 40, 'parent': <RootLogger root (WARNING)>, 'propagate': True, 'handlers': [], 'disabled': False, '_cache': {}, 'manager': <logging.Manager object at 0x7f7c29e34400>}
>>> module_logger.parent.__dict__
{'filters': [], 'name': 'root', 'level': 30, 'parent': None, 'propagate': True, 'handlers': [<StreamHandler <stderr> (NOTSET)>], 'disabled': False, '_cache': {}}
>>> logger = logging.getLogger('__main__')
>>> logger.setLevel(logging.INFO)
>>> logger.__dict__
{'filters': [], 'name': '__main__', 'level': 20, 'parent': <RootLogger root (WARNING)>, 'propagate': True, 'handlers': [], 'disabled': False, '_cache': {}, 'manager': <logging.Manager object at 0x7f7c29e34400>}
The root logger’s handler comes with a formatter too. This is why now the messages have different style
>>> module_logger.parent.handlers[0].formatter._fmt
'%(levelname)s:%(name)s:%(message)s'
The last resort handler didn’t have a formatter
>>> logging.lastResort.__dict__
{'filters': [], '_name': None, 'level': 30, 'formatter': None, '_closed': False, 'lock': <unlocked _thread.RLock object owner=0 count=0 at 0x7f99c8b77c40>}