Search code examples
pythonhttp-redirectloggingstdoutstderr

Redirecting Loggers`messages to sys.stdout and sys.stderr


I´m just starting to learn Python and have encountered a Problem that I´m not able to solve. I want to redirect every level above CRITICAL to sys.stderr and everything above WARNING to sys.stdout. I came up with this script...

import logging
import sys


print("imported module {}".format(__name__))


class PyLogger(logging.Logger):
    """Wrapper for logging.Logger to redirect its message to
    sys.stdout or sys.stderr accordingly """

    def __init__(self, *args):
        super(PyLogger, self).__init__(self, *args)

        # get Logger
        logger = logging.getLogger()
        logger.setLevel(logging.DEBUG)

        # build Formatter
        formatter = logging.Formatter(fmt="%(asctime)s:%(name)s   %(message)s")

        # build StreamHandler for sys.stderr
        error = logging.StreamHandler(stream=sys.stderr)
        error.setLevel(logging.CRITICAL)
        error.setFormatter(formatter)
        logger.addHandler(error)

        # build StreamHandler for sys.stdin
        out = logging.StreamHandler(stream=sys.stdout)
        out.setFormatter(formatter)
        out.setLevel(logging.WARNING)
        logger.addHandler(out)


def main():
    logger = PyLogger()
    # help(logger)
    logger.info("INFO")


if __name__ == "__main__":
    main()

When running this scrip directly I get the following error:

No handlers could be found for logger "<__main__.PyLogger object at 0x105f23c50>"

I´ve googled around and many people said that a logging.basicConfig() would do the job but that didn´t worked for me.

Maybe someone of you guys could help me out. Thanks!


Solution

  • Your class subclasses logging.Logger, so you should not call getLogger or manipulate a logger as an attribute. Rather, the logger is self inside the class, and should be adjusted directly:

    import logging
    import sys
    
    
    print("imported module {}".format(__name__))
    
    
    class PyLogger(logging.Logger):
        """Wrapper for logging.Logger to redirect its message to                                                                                   
        sys.stdout or sys.stderr accordingly """
    
        def __init__(self, *args):
            super(PyLogger, self).__init__(self, *args)
    
            #####
            # self *is* the logger!                                                                                                                          
            self.setLevel(logging.DEBUG)
    
            # build Formatter                                                                                                                      
            formatter = logging.Formatter(fmt="%(asctime)s:%(name)s   %(message)s")
    
            # build StreamHandler for sys.stderr                                                                                                   
            error = logging.StreamHandler(stream=sys.stderr)
            error.setLevel(logging.CRITICAL)
            error.setFormatter(formatter)
    
            #####
            # Assign the handler to self
            self.addHandler(error)
    
            # build StreamHandler for sys.stdin                                                                                                    
            out = logging.StreamHandler(stream=sys.stdout)
            out.setFormatter(formatter)
            out.setLevel(logging.WARNING)
    
            #####
            # Assign the handler to self
            self.addHandler(out)
    
    
    def main():
        logger = PyLogger()
        # help(logger)                                                                                                                             
        logger.info("INFO")
        logger.warning("WARN")
        logger.critical("CRIT")
    
    
    if __name__ == "__main__":
        main()
    

    This displays the following, as expected:

    ely@eschaton:~/programming$ python test_logger.py 
    imported module __main__
    2018-03-01 11:59:41,896:<__main__.PyLogger object at 0x7fa236aa4a50>   WARN
    2018-03-01 11:59:41,896:<__main__.PyLogger object at 0x7fa236aa4a50>   CRIT
    2018-03-01 11:59:41,896:<__main__.PyLogger object at 0x7fa236aa4a50>   CRIT
    

    Notice how the critical message trips two different output handlers, so it appears twice (once because it satisfied warning level, once for critical level).

    In your original code, notice that you are creating a variable called logger inside of __init__, but this not assigned to self or anything. This variable gets destroyed when it goes out of scope of the __init__ function, and so the assignment of any handlers is meaningless. Plus, because handlers weren't assigned to self, but the object that self is referencing is the logger that will be called later on, that is why you see the error about no handlers.