Here is what I learned:
It looks like using
basicConfig()
sets some global variables for logging. Changing the logging level seems to change it globally rather than just for this call.Whether implementing a decorator as a class or as a function seems to make a difference. While not totally sure, I believe that by implementing my
self.setConfig
in__init__
, I changed the class for all functions I decorated. I deduct this from the solution I found, which is implementing the decorator as a function:
This it what seems to work (even though I'm a little puzzled about the order in which things are returned):
def log_with_func(funcentry='Running {}',funcexit='Returning {}',setConfig=None):
ENTRY_MESSAGE = funcentry
EXIT_MESSAGE = funcexit
def func(f):
@functools.wraps(f)
def wrapper(*args, **kwds):
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
logger = logging.getLogger(__name__)
if setConfig:
logging.basicConfig(**setConfig)
else:
logging.basicConfig(level=logging.INFO)
logger.info(ENTRY_MESSAGE.format(f.__name__)+'\n\n')
logger.debug("The following arguments have been received:\n{}\n\nThe following keyword arguments have been received:\n{}\n\n".format(args,kwds))
try:
f_result = f(*args, **kwds)
logger.info(EXIT_MESSAGE.format(f.__name__))
return f_result
except Exception:
logger.exception("An exception was raised:\n\n")
return wrapper
return func
Applied it looks like this:
In [24]: @log_with_func()
...: def aa(msg):
...: print(msg + 'from a')
...:
In [25]: @log_with_func(setConfig={'level':logging.DEBUG})
...: def bb(msg):
...: print(msg + 'from b')
...:
In [26]: print(aa('OMG!!!'))
...: print(bb('OMG!!!'))
...: print(aa('OMG!!!'))
...:
INFO:__main__:Running aa
INFO:__main__:Returning aa
INFO:__main__:Running bb
DEBUG:__main__:The following arguments have been received:
('OMG!!!',)
The following keyword arguments have been received:
{}
INFO:__main__:Returning bb
INFO:__main__:Running aa
INFO:__main__:Returning aa
OMG!!!from a
None
OMG!!!from b
None
OMG!!!from a
None
In [27]: