Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Format log messages as a tree

Tags:

python

logging

I would like to have python logging output to be in form of tree corresponding to logger tree. Just look at example.

Lets say we have a code:

import logging

logger_a = logging.getLogger("a")
logger_a_b = logging.getLogger("a.b")
logger_a_b_c = logging.getLogger("a.b.c")
# ...


logger_a.debug("One")

logger_a_b.warning("two")
logger_a_b.warning("three")

logger_a_b_c.critical("Four")

logger_a_b.warning("Five")

The output should be something like:

<--"a"
   |   DEBUG: One
   |
   o<--"a.b"
   |   | WARNING: Two
   |   | WARNING: Three
   |   |
   |   o<--"a.b.c"
   |       | CRITICAL: Four   
   |       |
   |   | WARNING: Five

I could write formatters for each of the log by hand, but it doesn't solve the problem of inserting something like o<--"a.b" right and I would prefer to calculate offset automatically by logging structure.

There is a module called logging tree. It prints the logging layout. What I would like, is to print log messages approximately the same way.

Do you know any libraries, ways of doing it easy way?

like image 613
MajesticRa Avatar asked Oct 18 '22 17:10

MajesticRa


1 Answers

Based on your example, I created a custom Formatter which will handle the tree.

import logging

# custom tree formatter
class TreeFormatter(logging.Formatter):
    formatPrefix = {} # map loggername, formatPrefix

    def format(self, record):
        s = ""
        # first time this name is encountered: create the prefix and print the name
        if not record.name in self.formatPrefix: 
            f = self.getFormatPrefix(record)
            s += "%s \"%s\"\n" % (f, record.name)

        # print the actual message
        s += "%s %s: %s" % (self.formatPrefix[record.name], record.levelname, record.msg) 
        return s


    # create the format prefix for the given package name 
    # (stored in self.formatPrefix[record.name])
    # and return the first line to print
    def getFormatPrefix(self, record):
        depth = record.name.count(".")
        self.formatPrefix[record.name] = "   |" * (depth+1)

        if depth == 0:
            return "<--"

        return "%so<--" % ( ("   |" * depth)[:-1])

You can then use it to create the first-level logger (here a). The rest of the code is unchanged.

Here is an example:

# use this to create the first level logger
def createTreeLogger(name, level=logging.DEBUG):
    logger = logging.getLogger(name)
    logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    ch.setFormatter(TreeFormatter())
    logger.addHandler(ch)
    return logger


if __name__ == '__main__':

    logger_a = createTreeLogger("a") # first level: use createLogger
    # then create your loggers as always
    logger_a_b = logging.getLogger("a.b") 
    logger_a_b_c = logging.getLogger("a.b.c")


    logger_a.debug("One")

    logger_a_b.warning("two")
    logger_a_b.warning("three")

    logger_a_b_c.critical("Four")

    logger_a_b.warning("Five")
    logger_a.warning("Six")

What is nice is that the logging package internals will automatically use the same handler for the subpackages (a.b, a.b.c). So, by running this code, you get:

<-- "a"
   | DEBUG: One
   o<-- "a.b"
   |   | WARNING: two
   |   | WARNING: three
   |   o<-- "a.b.c"
   |   |   | CRITICAL: Four
   |   | WARNING: Five
   | WARNING: Six

One drawback is that the logs become confusing if you have more than one package hierarchy. But the TreeFormatter class is easy to tweak to your needs.

like image 165
Derlin Avatar answered Oct 21 '22 07:10

Derlin