Python 在装饰器中单击基于选项的登录
Python click option based logging in decorator
新的开始。我有一个 CLI 应用程序,它使用 click 来处理参数解析。对于主要的“可执行”脚本,我有一个定义的详细标志(-v、-vv、-vvv、...)来控制日志记录的详细程度。我想“跟踪”特定功能的功能调用。下面是一个示例,希望能说明问题。
import click
import logging
import functools
class MyLogger(object):
def __init__(self):
self.__logger = None
def init_logger(self, name, verbosity):
logging_levels = {0: logging.CRITICAL,
1: logging.ERROR,
2: logging.INFO,
3: logging.DEBUG}
logging.basicConfig(level=logging_levels.get(verbosity, logging.WARNING))
self.__logger = logging.getLogger(name)
@property
def logger(self):
return self.__logger
myLogger = MyLogger()
class TraceFunction(object):
def __init__(self, logger):
self.logger = logger
def __call__(self, function):
name = function.__name__
@functools.wraps(function)
def wrapped(*args, **kwargs):
self.logger.debug(f'{name}({list(*args)}, {dict(**kwargs)})')
result = function(*args, **kwargs)
self.logger.debug(f'{result}')
return result
return wrapped
# (1) @TraceFunction(myLogger.logger)
def echo(message):
return message.upper()
@click.command('echo')
@click.option('-e', '--echo', 'message', required=True, type=str)
def echo_command(message):
myLogger.logger.info('echo_command')
return echo(message)
@click.group()
@click.option('-v', 'verbosity', count=True)
def main(verbosity: int):
myLogger.init_logger(__name__, verbosity)
# (2) TraceFunction(myLogger.logger)(echo)
myLogger.logger.info('main')
if __name__ == '__main__':
main.add_command(echo_command)
main()
以上如果执行将正确产生以下输出:
script.py -vv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command
我想“追踪”函数:echo。更准确地说,我想用实际参数和返回值记录实际的函数调用。好的,不止于此,但我需要最少的样本。为此,我尝试了两件事,在评论中标有 (1) 和 (2)。
@TraceFunction(myLogger.logger)
def echo(message):
return message.upper()
它完全不起作用,因为我原来的问题 python 将执行 TraceFunction。call(echo) before in "main" I call init_logger 这基本上会配置记录器本身。作为 TraceFunction.call 的结果,记录器是 None,我得到:
AttributeError: 'NoneType' object has no attribute 'debug'
好吧,我可以稍后再注册,至少我是(2)想到的。好吧,异常肯定消失了,但是永远不会调用 call 中定义的“包装”,而且除了已经显示的
之外,再一次没有记录任何内容
script.py -vvv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command
@更新
按照 afterburner 的回答,事情会更进一步,但它没有做它应该做的事情:
script.py -vvv echo -e "Hello World"
DEBUG:__main__:echo(['F','o','o'],{})
DEBUG:__main__:FOO
INFO: __main__:main
INFO: __main__:echo_command
意料之中的井。另一方面,预期输出为:
script.py -vvv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command
DEBUG:__main__:echo(['Hello World'],{})
DEBUG:__main__:HELLO WORLD
所以,我看到的主要问题是您没有调用包装函数。
TraceFunction(myLogger.logger)(echo)
# vs
TraceFunction(myLogger.logger)(echo)()
我还对您的代码进行了一些更改,但主要问题是 wrapped
函数从未被调用过。
class MyLogger(object):
def __init__(self):
self.__logger = None
def init_logger(self, name, verbosity):
# extract log level based on verbosity flag
logging_levels = [logging.CRITICAL, logging.INFO, logging.DEBUG]
logging.basicConfig(level=logging_levels[verbosity])
self.__logger = logging.getLogger(name)
@property
def logger(self):
return self.__logger
myLogger = MyLogger()
class TraceFunction(object):
def __init__(self, logger):
self.logger = logger
def __call__(self, function):
name = function.__name__
@functools.wraps(function)
def wrapped(*args, **kwargs):
# improved the formatting of arguments
nicely_formatted_args = ', '.join(args)
nicely_formatted_kwargs = ', '.join(kwargs)
arguments = nicely_formatted_args
if nicely_formatted_kwargs != '':
arguments = f'{arguments}, {nicely_formatted_kwargs}'
self.logger.debug(f'{name}({arguments})')
result = function(*args, **kwargs)
self.logger.debug(f'{result}')
return result
return wrapped
# (1) @TraceFunction(myLogger.logger)
def echo(message):
return message.upper()
@click.command('echo')
@click.option('-e', '--echo', 'message', required=True, type=str)
def echo_command(message):
myLogger.logger.info('echo_command')
return echo(message)
@click.group()
@click.option('-v', 'verbosity', count=True) # <- made verbosity a count argument so you can extract all of your levels based on -v -vv -vvv etc.
def main(verbosity):
myLogger.init_logger(__name__, verbosity)
# (2)
# Invoking the function with argument 'Foo'
TraceFunction(myLogger.logger)(echo)("Foo")
def run_logging():
main.add_command(echo_command)
main()
if __name__ == '__main__':
run_logging()
我设法让它工作了,但它确实很难看……至少在目前的形式下是这样。唯一需要的更改是:
@click.group()
@click.option('-v', 'verbosity', count=True)
def main(verbosity: int = 0):
global echo
myLogger.init_logger(__name__, verbosity)
echo = TraceFunction(myLogger.logger)(echo) # <<< !!!
myLogger.logger.info('main')
这样做,输出变为:
INFO:__main__:main
INFO:__main__:echo_command
TRACE:__main__:echo(['Hello World'], {})
TRACE:__main__:HELLO WORLD
所以答案是,我完全错过了:
TraceFunction(myLogger.logger)(echo)
很好,但我需要将它分配给原始的 echo 函数:
echo = TraceFunction(myLogger.logger)(echo)
新的开始。我有一个 CLI 应用程序,它使用 click 来处理参数解析。对于主要的“可执行”脚本,我有一个定义的详细标志(-v、-vv、-vvv、...)来控制日志记录的详细程度。我想“跟踪”特定功能的功能调用。下面是一个示例,希望能说明问题。
import click
import logging
import functools
class MyLogger(object):
def __init__(self):
self.__logger = None
def init_logger(self, name, verbosity):
logging_levels = {0: logging.CRITICAL,
1: logging.ERROR,
2: logging.INFO,
3: logging.DEBUG}
logging.basicConfig(level=logging_levels.get(verbosity, logging.WARNING))
self.__logger = logging.getLogger(name)
@property
def logger(self):
return self.__logger
myLogger = MyLogger()
class TraceFunction(object):
def __init__(self, logger):
self.logger = logger
def __call__(self, function):
name = function.__name__
@functools.wraps(function)
def wrapped(*args, **kwargs):
self.logger.debug(f'{name}({list(*args)}, {dict(**kwargs)})')
result = function(*args, **kwargs)
self.logger.debug(f'{result}')
return result
return wrapped
# (1) @TraceFunction(myLogger.logger)
def echo(message):
return message.upper()
@click.command('echo')
@click.option('-e', '--echo', 'message', required=True, type=str)
def echo_command(message):
myLogger.logger.info('echo_command')
return echo(message)
@click.group()
@click.option('-v', 'verbosity', count=True)
def main(verbosity: int):
myLogger.init_logger(__name__, verbosity)
# (2) TraceFunction(myLogger.logger)(echo)
myLogger.logger.info('main')
if __name__ == '__main__':
main.add_command(echo_command)
main()
以上如果执行将正确产生以下输出:
script.py -vv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command
我想“追踪”函数:echo。更准确地说,我想用实际参数和返回值记录实际的函数调用。好的,不止于此,但我需要最少的样本。为此,我尝试了两件事,在评论中标有 (1) 和 (2)。
@TraceFunction(myLogger.logger)
def echo(message):
return message.upper()
它完全不起作用,因为我原来的问题 python 将执行 TraceFunction。call(echo) before in "main" I call init_logger 这基本上会配置记录器本身。作为 TraceFunction.call 的结果,记录器是 None,我得到:
AttributeError: 'NoneType' object has no attribute 'debug'
好吧,我可以稍后再注册,至少我是(2)想到的。好吧,异常肯定消失了,但是永远不会调用 call 中定义的“包装”,而且除了已经显示的
之外,再一次没有记录任何内容script.py -vvv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command
@更新 按照 afterburner 的回答,事情会更进一步,但它没有做它应该做的事情:
script.py -vvv echo -e "Hello World"
DEBUG:__main__:echo(['F','o','o'],{})
DEBUG:__main__:FOO
INFO: __main__:main
INFO: __main__:echo_command
意料之中的井。另一方面,预期输出为:
script.py -vvv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command
DEBUG:__main__:echo(['Hello World'],{})
DEBUG:__main__:HELLO WORLD
所以,我看到的主要问题是您没有调用包装函数。
TraceFunction(myLogger.logger)(echo)
# vs
TraceFunction(myLogger.logger)(echo)()
我还对您的代码进行了一些更改,但主要问题是 wrapped
函数从未被调用过。
class MyLogger(object):
def __init__(self):
self.__logger = None
def init_logger(self, name, verbosity):
# extract log level based on verbosity flag
logging_levels = [logging.CRITICAL, logging.INFO, logging.DEBUG]
logging.basicConfig(level=logging_levels[verbosity])
self.__logger = logging.getLogger(name)
@property
def logger(self):
return self.__logger
myLogger = MyLogger()
class TraceFunction(object):
def __init__(self, logger):
self.logger = logger
def __call__(self, function):
name = function.__name__
@functools.wraps(function)
def wrapped(*args, **kwargs):
# improved the formatting of arguments
nicely_formatted_args = ', '.join(args)
nicely_formatted_kwargs = ', '.join(kwargs)
arguments = nicely_formatted_args
if nicely_formatted_kwargs != '':
arguments = f'{arguments}, {nicely_formatted_kwargs}'
self.logger.debug(f'{name}({arguments})')
result = function(*args, **kwargs)
self.logger.debug(f'{result}')
return result
return wrapped
# (1) @TraceFunction(myLogger.logger)
def echo(message):
return message.upper()
@click.command('echo')
@click.option('-e', '--echo', 'message', required=True, type=str)
def echo_command(message):
myLogger.logger.info('echo_command')
return echo(message)
@click.group()
@click.option('-v', 'verbosity', count=True) # <- made verbosity a count argument so you can extract all of your levels based on -v -vv -vvv etc.
def main(verbosity):
myLogger.init_logger(__name__, verbosity)
# (2)
# Invoking the function with argument 'Foo'
TraceFunction(myLogger.logger)(echo)("Foo")
def run_logging():
main.add_command(echo_command)
main()
if __name__ == '__main__':
run_logging()
我设法让它工作了,但它确实很难看……至少在目前的形式下是这样。唯一需要的更改是:
@click.group()
@click.option('-v', 'verbosity', count=True)
def main(verbosity: int = 0):
global echo
myLogger.init_logger(__name__, verbosity)
echo = TraceFunction(myLogger.logger)(echo) # <<< !!!
myLogger.logger.info('main')
这样做,输出变为:
INFO:__main__:main
INFO:__main__:echo_command
TRACE:__main__:echo(['Hello World'], {})
TRACE:__main__:HELLO WORLD
所以答案是,我完全错过了:
TraceFunction(myLogger.logger)(echo)
很好,但我需要将它分配给原始的 echo 函数:
echo = TraceFunction(myLogger.logger)(echo)