Reputation: 23381
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
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
Reputation: 374
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
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
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
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
$ 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
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
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
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