How to use multi logger in Python

194 Views Asked by At

I try to use two different loggers to handle different log levels. For example, I want info message store in a file and don't log error message. Error messages of some special functions will email to the user.

I write a simple program to test the logging module.

Code:

import logging

def_logger = logging.getLogger("debuglogger")
def_logger.setLevel(logging.DEBUG)

maillogger = logging.getLogger("mail")
maillogger.setLevel(logging.ERROR)
mailhandler = logging.StreamHandler()
mailhandler.setLevel(logging.ERROR)
mailhandler.setFormatter(logging.Formatter('Error:  %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
maillogger.addHandler(mailhandler)


print(def_logger.getEffectiveLevel())
print(maillogger.getEffectiveLevel())
def_logger.info("info 1")
maillogger.info("info 2")
def_logger.error("error 1")
maillogger.error("error 2")

Output: Output result

I can see the level of them is correct, but both of them act like the level is ERROR.

How can I correctly configure them?

Answer: Base on blues advice, I added a handler and it solved my problem. Here is the modified code:

import logging

def_logger = logging.getLogger("debuglogger")
def_logger.setLevel(logging.DEBUG)
def_logger.addHandler(logging.StreamHandler()) #added a handler here

maillogger = logging.getLogger("mail")
maillogger.setLevel(logging.ERROR)
mailhandler = logging.StreamHandler()
mailhandler.setLevel(logging.ERROR)
mailhandler.setFormatter(logging.Formatter('Error:  %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
maillogger.addHandler(mailhandler)


print(def_logger.getEffectiveLevel())
print(maillogger.getEffectiveLevel())
def_logger.info("info 1")
maillogger.info("info 2")
def_logger.error("error 1")
maillogger.error("error 2")
2

There are 2 best solutions below

1
On BEST ANSWER

Neither the def_logger nor any of its parents have a handler attached to it. So what happens is that the logging module falls back to logging.lastResort which by default is a StreamHandler with level Warning. That is the reason why the info message doesn't appear, while the error does. So to solve your problem attach a handler to the def_logger.

Note: In your scenario the only parent both loggers have is the default root handler.

0
On

You could add a filter for each logger or handler to process only records of interest

import logging


def filter_info(record):
    return True if record.levelno == logging.INFO else False


def filter_error(record):
    return True if record.levelno >= logging.ERROR else False


# define debug logger
def_logger = logging.getLogger("debuglogger")
def_logger.setLevel(logging.DEBUG)
def_logger.addFilter(filter_info)               # add filter directly to this logger since you didn't define any handler 

# define handler for mail logger
mail_handler = logging.StreamHandler()
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(logging.Formatter('Error:  %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
mail_handler.addFilter(filter_error)            # add filter to handler 

mail_logger = logging.getLogger("mail")
mail_logger.setLevel(logging.ERROR)
mail_logger.addHandler(mail_handler)

# test
def_logger.info("info 1")
mail_logger.info("info 2")
def_logger.error("error 1")
mail_logger.error("error 2")

If filter return True, the log record is processed. Otherwise, it is skipped.

Note:

  • Filter attached to a logger won't be called for log record that is generated by descendant loggers. For example, if you add a filter to logger A, it won't be called for records that are generated by logger A.B nor A.B.C.

  • Filter attached to a handler is consulted before an event is emitted by that handler.

This means that you just need one logger and add two handlers with different filters attached.

import logging


def filter_info(record):
    return True if record.levelno == logging.INFO else False


def filter_error(record):
    return True if record.levelno >= logging.ERROR else False


# define your logger
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)

# define handler for file
file_handler = logging.FileHandler('path_to_log.txt')
file_handler.level = logging.INFO
file_handler.addFilter(filter_info)      # add filter to handler

# define handler for mail
mail_handler = logging.StreamHandler()
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(logging.Formatter('Error:  %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
mail_handler.addFilter(filter_error)     # add filter to handler

logger.addHandler(file_handler)
logger.addHandler(mail_handler)

# test
logger.info("info 1")
logger.info("info 2")
logger.error("error 1")
logger.error("error 2")