loguru
loguru copied to clipboard
How to log the class of a method when subclassing
I am creating a library that has two classes. One of the classes is a subclass and I wanted to customize the logging to reflect where the logs were coming from. I took inspiration from here #430 but ran into issues regarding subclassing.
So I came up with this solution.
File Structure: main.py my_classes/ └───my_classes.py └───__init__.py
my_classes/init.py
from .my_classes import Parent, Child
from loguru import logger
logger.disable(__name__)
my_classes/my_classes.py
from loguru import logger
class Parent:
def __init__(self):
self.__logger.info("Parent __init__")
def parent_function(self):
self.__logger.info("Called from Parent")
__logger = logger.patch(lambda record: record.update(name=__class__.__name__))
class Child(Parent):
def __init__(self):
self.__logger.info('Child __init__')
super().__init__()
def parent_function(self):
self.__logger.info("Called from Child")
super().parent_function()
__logger = logger.patch(lambda record: record.update(name=__class__.__name__))
main.py
from loguru import logger
from my_classes import Child
logger.enable("my_classes")
child = Child()
logger.debug('Initialized')
child.parent_function()
Output
2023-09-07 11:52:08.700 | INFO | Child:__init__:17 - Child __init__
2023-09-07 11:52:08.700 | INFO | Parent:__init__:7 - Parent __init__
2023-09-07 11:52:08.700 | DEBUG | __main__:<module>:5 - Initialized
2023-09-07 11:52:08.700 | INFO | Child:parent_function:21 - Called from Child
2023-09-07 11:52:08.716 | INFO | Parent:parent_function:10 - Called from Parent
While the output is correct and what I am looking for, my question is this the proper way of achieving the goal of correctly seeing what class is emitting the log message? My gut feeling is this is not the best way to handle this use case.
Thanks for any advice!
I would not advise to override the name
but rather provide a new parameter using bind()
.
import sys
from loguru import logger
class Parent:
def __init__(self):
self._logger = logger.bind(name=self.__class__.__name__)
def parent_function(self):
self._logger.info("Called from Parent")
class Child(Parent):
def __init__(self):
super().__init__()
self._logger.info("Child __init__")
def parent_function(self):
self._logger.info("Called from Child")
super().parent_function()
logger.remove()
logger.add(sys.stderr, format="{extra[name]} {message}")
c = Child()
c.parent_function()
The default "name" is useful for filtering logs by module name, but changing it could cause unexpected behavior.
Using logger.bind()
is also better for performance, as it avoids having to call the patching function continuously.
That doesn't provide the detail that I am needing in the logs though.
Your example:
...
fmt = "<g>{time}</> | <lvl>{level}</> | <c>{extra[name]}:{function}:{line}</> - {message}"
logger.add(sys.stderr, format=fmt)
c = Child()
c.parent_function()
Example output:
2023-09-08T07:56:07.581023-0400 | INFO | Child:__init__:16 - Child __init__
2023-09-08T07:56:07.581023-0400 | INFO | Child:parent_function:19 - Called from Child
2023-09-08T07:56:07.581023-0400 | INFO | Child:parent_function:10 - Called from Parent
All the the names are from Child
, I would like to know when the functions of Parent
are being called. Like So,
c.parent_function()
2023-09-07 11:52:08.700 | INFO | Child:parent_function:21 - Called from Child
2023-09-07 11:52:08.716 | INFO | Parent:parent_function:10 - Called from Parent
Also how and where am I suppose to adjust the format with this recipe example? Because this is going to be used as a library.
Thanks for any insight.
Well, in this case I think you have no other choice but to declare it twice and use "private" attribute:
class Parent:
def __init__(self):
self.__logger = logger.bind(name=__class__.__name__)
def parent_function(self):
self.__logger.info("Called from Parent")
class Child(Parent):
def __init__(self):
super().__init__()
self.__logger = logger.bind(name=__class__.__name__)
self.__logger.info("Child __init__")
def parent_function(self):
self.__logger.info("Called from Child")
super().parent_function()
Also how and where am I suppose to adjust the format with this recipe example? Because this is going to be used as a library.
If you intend to publish a library which uses Loguru, make sure to not automatically configure the logger
and too add any handler by default. This should be the responsibility of the user. Otherwise, your configuration might conflicts with the user one.
What is your use case for wanting to configure a logging format while developing a library?
If you really have to, I'll suggest to provide some kind of activate_logging()
function that must be called explicitly by your users if they are not familiar with Loguru.