c00kiemonster
c00kiemonster

Reputation: 23381

How do I get the name of the class containing a logging call in Python?

If I want the function name I can simply include %(funcName)s in the Formatter. But how do I get the name of the class containing the logging call instead?

I've gone through the documentation for logging, but I can't find any mentioning of it.

Upvotes: 36

Views: 47963

Answers (7)

Yang Bo
Yang Bo

Reputation: 3723

Just create the logger with the name f"{__class__.__module__}.{__class__.__qualname__}". __class__ is better than self.__class__ suggested by other answers, because it tells where the logger is actually defined, not just the final class name.

In order to access __class__, the logger needs to be defined in a method, not a ClassVar. The logger can be decorated with @staticmethod and @cache for reusing (or @cached_staticproperty).

from functools import cache
import logging


class MyClass:
    @staticmethod
    @cache
    def logger():
        return logging.getLogger(f"{__class__.__module__}.{__class__.__qualname__}")

    def __init__(self):
        __class__.logger().info("Initializing MyClass")

class MyClass2(MyClass):    
    @staticmethod
    @cache
    def logger():
        return logging.getLogger(f"{__class__.__module__}.{__class__.__qualname__}")

    def __init__(self):
        super().__init__()
        __class__.logger().info("Initializing MyClass2")

Now test it:

logging.basicConfig(level=logging.INFO)
MyClass()

We will see the log message includes the class name.

INFO:__main__.MyClass:Initializing MyClass

Because the loggers are static properties defined on each class, __class__.logger will be separate instances, so that it can tell in which class the logger is defined.

logging.basicConfig(level=logging.INFO)
MyClass2()
INFO:__main__.MyClass:Initializing MyClass
INFO:__main__.MyClass2:Initializing MyClass2

Upvotes: 1

You should use extra argument:

views.py

import logging

class SampleClass():
    def sample_func(self):
        logging.getLogger('info_logger').info('some text', extra={'className': self.__class__.__name__})

logger_settings.py

'format': '%(className)s | %(message)s ',

output log:

INFO | SampleClass | "some text" 

Upvotes: 5

thorfi
thorfi

Reputation: 327

Yet another approach if you also want the module name:

class MyClass(object):
    @property
    def logger(self):
        return logging.getLogger(f"{__name__}.{self.__class__.__name__}")

    def what(self, ever):
        self.logger.info("%r", ever)

Upvotes: 6

Joe Heffer
Joe Heffer

Reputation: 845

This is a function to make an informative log message using the representation class method:

https://docs.python.org/3/library/functions.html#repr

def log_message(thing: object = None, message: str = '') -> str:
    """:returns: detailed error message using reflection"""
    return '{} {}'.format(repr(thing), message)

This can be implemented to any class using a mix-in:

class UtilMixin(object):
    def log(self, message: str = '') -> str:
        """:returns: Log message formatting"""
        return log_message(thing=self, message=message)

You can than be associated with a class using multiple inheritance:

class MyClass(object, UtilMixin):
    def __repr__(self) -> str:
        return '<{}>'.format(self)
    pass

Usage

logger.warning(self.log('error message goes here'))

Upvotes: 0

kylehuff
kylehuff

Reputation: 5403

For a rather easy, pythonic way to get the class name to output with your logger, simply use a logging class.

import logging


# Create a base class
class LoggingHandler:
    def __init__(self, *args, **kwargs):
        self.log = logging.getLogger(self.__class__.__name__)


# Create test class A that inherits the base class
class testclassa(LoggingHandler):
    def testmethod1(self):
        # call self.log.<log level> instead of logging.log.<log level>
        self.log.error("error from test class A")


# Create test class B that inherits the base class
class testclassb(LoggingHandler):
    def testmethod2(self):
        # call self.log.<log level> instead of logging.log.<log level>
        self.log.error("error from test class B")


testclassa().testmethod1()
testclassb().testmethod2()

By naming the logger as above, the %(name)s will be the name of your class

example output

$ python mymodule.py
[2016-02-03 07:12:25,624] ERROR [testclassa.testmethod1:29] error from test class A
[2016-02-03 07:12:25,624] ERROR [testclassb.testmethod2:36] error from test class B

Alternative(s)

Non-inheritance

import logging


def log(className):
    return logging.getLogger(className)


class testclassa:
    def testmethod1(self):
        log(self.__class__.__name__).error("error from test class A")


class testclassb:
    def testmethod2(self):
        log(self.__class__.__name__).error("error from test class B")


testclassa().testmethod1()
testclassb().testmethod2()

Upvotes: 36

Tourman
Tourman

Reputation: 78

I personally just tend to name my loggers after classes, as it makes it much easier to track down where a particular message came from. So you can have a root logger named "top", and for the module "a" and class "testclass", I name my logger "top.a.testclass".

I don't see the need to otherwise retrieve the classname, since the log message should give you all the information you need.

@ed's response above, it feels very unpythonic to me and it is not something I would be comfortable with using on production code.

Upvotes: 0

ed.
ed.

Reputation: 1393

There is almost certainly a better way of doing this, but until someone points that out, this will work:

import inspect

class testclass:
    def testmethod(self):
        log()

def log():
    stack = inspect.stack()
    try:
        print "Whole stack is:"
        print "\n".join([str(x[4]) for x in stack])
        print "-"*20
        print "Caller was %s" %(str(stack[2][4]))
    finally:
        del stack

testclass().testmethod()

The output of this is the following:

Whole stack is:
['    stack = inspect.stack()\n']
['        f()\n']
['testclass().testmethod()\n']
['                exec code in self.locals\n']
['            ret = method(*args, **kwargs)\n']
None
--------------------
Caller was ['testclass().testmethod()\n']

Upvotes: 2

Related Questions