使用functools.wraps和日志记录装饰器

时间:2011-08-09 22:39:09

标签: python logging

我正在尝试编写一个简单的装饰器,在调用装饰函数之前记录给定的语句。记录的语句应该看起来都来自同一个函数,我认为这是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。

4 个答案:

答案 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

然而,可以使用日志装饰器:当它是日志源文件的一部分时。但是没有(还)