Short answer
To achieve exactly what you described, you need 3 handlers (which you have in your example), and set their default level to the corresponding level at the top. No need to set the handler's levels separately.
import logging
from logging import handlers
def my_logger(module_name, log_file, level):
logger = logging.getLogger(module_name)
logger.setLevel(level)
# Create handlers
c_handler = logging.StreamHandler()
f_handler = handlers.RotatingFileHandler(filename=log_file)
# Create formatters and add it to handlers
c_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(message)s')
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(message)s')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logger
logger.addHandler(c_handler)
logger.addHandler(f_handler)
return logger
if __name__ == "__main__":
logger_info = my_logger("test_info", "info.log", logging.INFO)
logger_debug = my_logger("test_debug", "debug.log", logging.DEBUG)
logger_error = my_logger("test_error", "error.log", logging.ERROR)
logger_info.info('Info test ...')
logger_debug.debug('Debug test ...')
logger_error.error('Error test ...')
Output:
python test_logger2.py
2020-12-04 16:57:00,771 - test_info - INFO - 29 - Info test ...
2020-12-04 16:57:00,771 - test_debug - DEBUG - 30 - Debug test ...
2020-12-04 16:57:00,771 - test_error - ERROR - 31 - Error test ...
cat info.log
2020-12-04 16:57:00,771 - test_info - INFO - 29 - Info test ...
cat debug.log
2020-12-04 16:57:00,771 - test_debug - DEBUG - 30 - Debug test ...
cat error.log
2020-12-04 16:57:00,771 - test_error - ERROR - 31 - Error test ...
Long answer:
Why 3 copies?
You are calling your my_logger
function 3 times, and each time you call it a new file handler as well as a NEW stream handler added to the logger. That's why you see 3 copies on your console (3 stream handlers). Plus all your handlers are set to DEBUG
level. That's why all three logger prints out any log that you have given. You don't want a ERROR
handler to process/print logs at DEBUG
/INFO
levels, so you should set its level to ERROR
.
I don't think this is the standard approach for logging. You should instead have single logger with 4 handlers (stream, file_debug, file_info, file_error). Additionally, a debug log file should include all logs, and an info log file should include info logs and error logs. Details below.
import logging
from logging import handlers
def main():
logger = logging.getLogger()
c_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(message)s')
# You need to set the default level to the lowest (DEBUG)
logger.setLevel(logging.DEBUG)
c_handler = logging.StreamHandler()
c_handler.setLevel(logging.DEBUG)
c_handler.setFormatter(c_format)
f1_handler = handlers.RotatingFileHandler("debug.log")
f1_handler.setLevel(logging.DEBUG)
f1_handler.setFormatter(c_format)
f2_handler = logging.handlers.RotatingFileHandler("info.log")
f2_handler.setLevel(logging.INFO)
f2_handler.setFormatter(c_format)
f3_handler = logging.handlers.RotatingFileHandler("error.log")
f3_handler.setLevel(logging.ERROR)
f3_handler.setFormatter(c_format)
logger.addHandler(c_handler)
logger.addHandler(f1_handler)
logger.addHandler(f2_handler)
logger.addHandler(f3_handler)
logger.debug("A debug line")
logger.info("An info line")
logger.error("An error line")
if __name__ == "__main__":
main()
The output is :
python test_logger.py
2020-12-04 16:48:56,247 - root - DEBUG - 32 - A debug line
2020-12-04 16:48:56,248 - root - INFO - 33 - An info line
2020-12-04 16:48:56,248 - root - ERROR - 34 - An error line
cat debug.log
2020-12-04 16:49:06,673 - root - DEBUG - 32 - A debug line
2020-12-04 16:49:06,673 - root - INFO - 33 - An info line
2020-12-04 16:49:06,673 - root - ERROR - 34 - An error line
cat info.log
2020-12-04 16:49:06,673 - root - INFO - 33 - An info line
2020-12-04 16:49:06,673 - root - ERROR - 34 - An error line
cat error.log
2020-12-04 16:49:06,673 - root - ERROR - 34 - An error line
Here you see your debug.log
file also contains the logs from other levels, and your info.log
file contain error logs too. Because that's the whole rationale behind the log levels. The lower level should also keep track of the logs of higher levels (DEBUG
< INFO
< WARNING
< ERROR
). That's why what you want is not a standard way of doing it, but can perfectly be achieved as described in the short answer.