I am trying to build a decorator that allows me to log functions in my classes in Python3. The decorator is as follows:
import functools
def log(_func=None, *, logger):
def decorator_log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
logger.info(f"Entering function {func.__qualname__}")
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
logger.debug(f"function {func.__qualname__} called with args {signature}")
try:
result = func(*args, **kwargs)
return result
except Exception as e:
logger.exception(f"Exception raised in {func.__qualname__}. exception: {str(e)}")
raise e
logger.info(f"Leaving function {func.__qualname__}.{func.__name__}")
except Exception:
pass
return wrapper
if _func is None:
return decorator_log
else:
return decorator_log(_func)
This is then used to decorate class functions like so:
class MainController(BaseController):
logger = logging.getLogger(__name__)
def __init__(self, main_model, main_view):
...
@log(logger=logger)
def initialize_components(self):
...
etc.
This works fine, but runs into issues when I use base classes and subclasses. In particular, I want to decorate almost all functions in both the base class and the subclass, but when I do this, func.__qualname__
will be subclass.methodname
only when the subclass overrides a base class method, and it will be the baseclass.methodname
otherwise.
How can I modify this approach so that even in cases where the subclass does not override the base class method, a decorator applied to the base class method will give me subclass.methodname
wherever the decorator call func.__qualname__
, instead of the base class?
I do not want to decorate it at the class level since in some cases there are multiple levels of inheritance from standard library classes and I only want to decorate functions that I define myself.
Instead of func.__qualname__
just use the class name along with the __name__
attribute:
def wrapper(*args, **kwargs):
self = args[0] # this will only work for methods
...
name = "self.__class__.__name__}.{func.__name__}"
logger.debug(f"function {name} called with args {signature}")
...
Of course, if you want the decorator to be applied to "stand-alone" functions, then the first argument (args[0]
, if it exists at all) won't be a reference to an instance: you have to adapt the code above to check for that, compute name
accordingly.