我正在尝试编写一个简单的装饰器,在调用装饰函数之前记录给定的语句。记录的语句应该看起来都来自同一个函数,我认为这是functools.wraps()的目的。
为什么以下代码:
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(funcName)20s - %(message)s')
from functools import wraps
def log_and_call(statement):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(statement)
return func(*args, **kwargs)
return wrapper
return decorator
@log_and_call("This should be logged by 'decorated_function'")
def decorated_function():
logging.info('I ran')
decorated_function()
导致日志语句如:
wrapper - This should be logged by 'decorated_function'
decorated_function - I ran
我认为对换行的调用会用wrapped_function的名称重命名包装器。
我正在使用python 2.7.1。
答案 0 :(得分:13)
不幸的是logging
使用函数代码对象来推断名称。您可以通过使用extra
关键字参数为记录指定一些其他属性来解决此问题,然后您可以在格式化期间使用这些属性。你可以这样做:
logging.basicConfig(
level=logging.DEBUG,
format='%(real_func_name)20s - %(message)s',
)
...
logging.info(statement, extra={'real_func_name': func.__name__})
这种方法的唯一缺点是你每次都必须传入extra
字典。为避免这种情况,您可以使用自定义格式化程序并将其覆盖funcName
:
import logging
from functools import wraps
class CustomFormatter(logging.Formatter):
"""Custom formatter, overrides funcName with value of name_override if it exists"""
def format(self, record):
if hasattr(record, 'name_override'):
record.funcName = record.name_override
return super(CustomFormatter, self).format(record)
# setup logger and handler
logger = logging.getLogger(__file__)
handler = logging.StreamHandler()
logger.setLevel(logging.DEBUG)
handler.setLevel(logging.DEBUG)
handler.setFormatter(CustomFormatter('%(funcName)20s - %(message)s'))
logger.addHandler(handler)
def log_and_call(statement):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# set name_override to func.__name__
logger.info(statement, extra={'name_override': func.__name__})
return func(*args, **kwargs)
return wrapper
return decorator
@log_and_call("This should be logged by 'decorated_function'")
def decorated_function():
logger.info('I ran')
decorated_function()
你想做什么:
% python logging_test.py
decorated_function - This should be logged by 'decorated_function'
decorated_function - I ran
答案 1 :(得分:2)
我在docs中发现了如何完成此操作,只需将此代码添加到您的装饰器中即可:
def log_and_call(statement):
def decorator(func):
old_factory = logging.getLogRecordFactory()
def record_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
record.funcName = func.__name__
return record
def wrapper(*args, **kwargs):
logging.setLogRecordFactory(record_factory)
logging.info(statement)
logging.setLogRecordFactory(old_factory)
return func(*args, **kwargs)
return wrapper
return decorator
或使用functools.wrap代替此装饰器:
def log_wrapper(func_overrider):
old_factory = logging.getLogRecordFactory()
def new_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
record.funcName = func_overrider.__name__
return record
def decorator(func):
def wrapper(*args, **kwargs):
logging.setLogRecordFactory(new_factory)
result = func(*args, **kwargs)
logging.setLogRecordFactory(old_factory)
return result
return wrapper
return decorator
答案 2 :(得分:0)
我怀疑日志记录模块在函数对象上使用__name__属性。即使您将该函数指定给另一个名称,这通常也不会改变...您会看到相同的结果:
def foo()
logging.info("in foo")
bar = foo
bar()
当您致电酒吧时,您将获得foo - in foo
,而非bar - in foo
。
装饰者正在做类似的事情。
答案 3 :(得分:0)
与您可能怀疑的不同,日志记录。函数不使用__name__属性。这意味着使用@wraps
(或手动设置包装器的__name__)不起作用!
相反,显示名称,即调用框架。它包含代码 -items(基本上是堆栈)的列表。有读取的函数名称,以及文件名和行号。使用logging-decorator时,wrapper-name总是打印,因为它是调用log的那个。
顺便说一句。日志记录。级别()函数都调用logging._log(*level*, ...)
,它也调用其他(日志)函数。所有最终都在堆栈上。为了防止显示这些日志功能,将搜索帧列表以查找第一个(最低)函数,该函数的文件名不是“记录”的一部分。这应该是记录的真正功能:一个调用记录器。 func ()。
遗憾的是,它是wrapper
。
然而,可以使用日志装饰器:当它是日志源文件的一部分时。但是没有(还)