Here are some good resources:
Briefly, (as far as I understand)
The logging module provides hierarchical loggers meaning if the root logger (the logger you get with logging.getLogger()
) is formatted in a certain way all the loggers with other names, (logging.getLogger("other_logger")
) will be formatted the same (unless if you set propagate
to False
)
The best practice for big projects as explained in the links above will be to define a logger configuration at the beginning of your package (i.e in __main__.py
) and then just call
logging.getLogger(__name__)
Example:
source code here
project:
logs
├── errors.log # will be created automatically
├── std.log # will be created automatically
src
├── animals
│ ├── __init__.py
│ ├── dog.py
| |── cat.py
| |── fish.py
└── main.py
Inside main.py:
import logging
from logging.config import dictConfig
LOG_CONFIG = {
'version': 1,
'handlers': {
'console': {
'level': 'DEBUG',
'formatter': 'std',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout'
},
'my_detailed_console': {
'level': 'WARNING',
'formatter': 'error',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout'
},
'std_fh': {
'level': 'INFO',
'formatter': 'std',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/std.log',
'mode': 'a',
'maxBytes': 1048576,
'backupCount': 10
},
'my_detailed_fh': {
'level': 'WARNING',
'formatter': 'error',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/errors.log',
'mode': 'a',
'maxBytes': 1048576,
'backupCount': 10
}
},
'loggers': {
'': { # root logger, all other loggers will be of this logger level implicitlly.
'level':'DEBUG',
'handlers': ['std_fh', 'console'],
},
'my_detailed': {
'level': 'WARNING',
'propagate': False,
'handlers': ['my_detailed_fh','my_detailed_console'],
},
'my_normal': {
'level': 'INFO',
'propagate': False,
'handlers': ['std_fh','console'],
}
},
'formatters': {
'std': {
'format': '[%(levelname)s - %(asctime)s - %(name)s::] %(message)s'
},
'error': {
'format': '[%(levelname)s - %(asctime)s - %(name)s - %(process)d::module :%(module)s|Line: %(lineno)s] messages:[ %(message)s ]'
},
}
}
logging.config.dictConfig(LOG_CONFIG)
root_logger = logging.getLogger(__name__) # this is a root logger
my_normal_logger = logging.getLogger('my_normal.' + __name__) # this is an `src` logger
my_detailed_logger = logging.getLogger('my_detailed.' + __name__) # this is 'my_detailed'
def main():
root_logger.debug("hello from root logger")
my_normal_logger.debug("won't print") # higher level needed
my_normal_logger.info("hello from my_normal logger")
my_detailed_logger.info("won't print") # higher level needed
my_detailed_logger.warning("hello from my_detailed logger")
import animals.cat
import animals.cow
import animals.fish
if __name__ == '__main__':
main()
This is what you do in all animals
# cat.py
import logging
logger = logging.getLogger(__name__)
logger.info('mew mew')
Run main.py
output:
[DEBUG - 2022-06-21 22:52:06,682 - __main__::] hello from root logger
[INFO - 2022-06-21 22:52:06,682 - my_normal.__main__::] hello from my_normal logger
[WARNING - 2022-06-21 22:52:06,682 - my_detailed.__main__ - 15177::module :main|Line: 78] messages:[ hello from my_detailed logger ]
[INFO - 2022-06-21 22:52:06,683 - animals.cat::] mew mew
[INFO - 2022-06-21 22:52:06,683 - animals.cow::] mooooo
[INFO - 2022-06-21 22:52:06,683 - animals.fish::] blop blop
Note: I would recommend using the dict
config instead of a file config for security measures (search here for eval()
).