将 functools.wraps 与日志装饰器一起使用 [英] Using functools.wraps with a logging decorator

查看:42
本文介绍了将 functools.wraps 与日志装饰器一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个简单的装饰器,该装饰器在调用装饰的函数之前记录给定的语句.记录的语句应该看起来都来自同一函数,我认为这是functools.wraps()的目的.

I'm trying to write a simple decorator that logs a given statement before calling the decorated function. The logged statements should both appear to come from the same function, which I thought was the purpose of functools.wraps().

为什么要执行以下代码:

Why does the following code:

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

我认为对wraps的调用会使用装饰函数名重命名包装器.

I thought the call to wraps would rename wrapper with decorated_function's name.

我正在使用python 2.7.1.

I'm using python 2.7.1.

推荐答案

不幸的是, logging 使用功能代码对象来推断名称.您可以使用 extra 关键字参数来解决此问题为记录指定一些其他属性,然后可以在格式化期间使用这些属性.您可以执行以下操作:

Unfortunately logging uses the function code object to infer the name. You could work around this by using the extra keyword argument to specify some additional attributes for the record, which you could then use during formatting. You could do something like:

logging.basicConfig(
    level=logging.DEBUG,
    format='%(real_func_name)20s - %(message)s',
)

...

logging.info(statement, extra={'real_func_name': func.__name__})

这种方法的唯一缺点是每次都必须传入 extra 字典.为避免这种情况,您可以使用自定义格式化程序,并使其覆盖 funcName :

The only downside to this approach is that you have to pass in the extra dictionary every time. To avoid that you could use a custom formatter and have it override 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

这篇关于将 functools.wraps 与日志装饰器一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆