9

I was trying to create a custom attribute for logging (caller's class name, module name, etc.) and got stuck with a strange exception telling me that the LogRecord instance created in the process did not have the necessary attributes. After a bit of testing I ended up with this:

import logging

class MyLogger(logging.getLoggerClass()):
    value = None

logging.setLoggerClass(MyLogger)

loggers = [
    logging.getLogger(),
    logging.getLogger(""),
    logging.getLogger("Name")
]

for logger in loggers:
    print(isinstance(logger, MyLogger), hasattr(logger, "value"))

This seemingly correct piece of code yields:

False False
False False
True True

Bug or feature?

2 Answers 2

6

Looking at the source code we can see the following:

root = RootLogger(WARNING)
def getLogger(name=None):
    if name:
        return Logger.manager.getLogger(name)
    else:
        return root

That is, a root logger is created by default when the module is imported. Hence, every time you look for the root looger (passing a false value such as the empty string), you're going to get a logging.RootLogger object regardless of any call to logging.setLoggerClass.

Regarding the logger class being used we can see:

_loggerClass = None
def setLoggerClass(klass):
    ...
    _loggerClass = klass

This means that a global variable holds the logger class to be used in the future.

In addition to this, looking at logging.Manager (used by logging.getLogger), we can see this:

def getLogger(self, name):
    ...
            rv = (self.loggerClass or _loggerClass)(name)

That is, if self.loggerClass isn't set (which won't be unless you've explicitly set it), the class from the global variable is used.

Hence, it's a feature. The root logger is always a logging.RootLogger object and the other logger objects are created based on the configuration at that time.

Sign up to request clarification or add additional context in comments.

Comments

2

logging.getLogger() and logging.getLogger("") don't return a MyLogger because they return the root logger of the logging hierarchy, as described in the logging documentation:

logging.getLogger([name])

Return a logger with the specified name or, if no name is specified, return a logger which is the root logger of the hierarchy.

Thus, as you have the logger set up:

>>> logging.getLogger()
<logging.RootLogger object at 0x7d9450>
>>> logging.getLogger("foo")
<test3.MyLogger object at 0x76d9f0>

I don't think this is related to the KeyError you started your post with. You should post the code that caused that exception to be thrown (test.py).

3 Comments

Thank you for your comment, I've edited the question. But I think that this behavior is not what one would expect to happen.
Why is it not what you would expect to happen? It is precisely what the documentation says will happen when no name is specified.
Even though this is what the documentation says, it is counter-intuitive. Setting a class for loggers, I would expect all loggers to have that class, including the root one and excluding the ones instantiated already.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.