2011-08-09 55 views
14

我正試圖編寫一個簡單的裝飾器,在調用裝飾函數之前記錄給定的語句。記錄的語句應該都來自同一個函數,我認爲這是functools.wraps()的目的。與記錄裝飾器一起使用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 

我想調用包裝將重命名decorated_function的名字包裝。

我正在使用python 2.7.1。

回答

10

不幸的是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 
+0

有沒有辦法在沒有通過聲明的情況下做同樣的事情。意味着我只想在我的代碼中使用logger.info('test {}',123),但它會自動附加一個額外的字典。現在我用你的想法。像def log_and_call(語句,級別):....和def test(st,level):@log_and_call()def api():傳遞api()。並使用像logger.debug('測試{}'。格式(123),'調試')。它的工作在那裏有更好的方法。 – Jisson

0

我懷疑記錄模塊在函數對象上使用了__name__屬性。也就是說,如果您分配的功能,以另一個名字通常不會改變,甚至......你會看到做類似相同的結果:

def foo() 
    logging.info("in foo") 
bar = foo 
bar() 

你會得到foo - in foo,不bar - in foo當你打電話吧。

裝飾者正在做一些類似的事情。

+2

在發佈之前,您是否閱讀過有關'wraps'的任何內容?它的全部目的是完全按照你所說的做不到的。 – agf

0

不像你可能會懷疑,日誌記錄。 函數不使用__name__屬性。這意味着使用@wraps(或手動設置包裝的__name__)不起作用!

而是顯示名稱,呼叫幀被檢查。它包含一列代碼 -items(基本上是堆棧)。有函數名稱讀取,以及文件名和行號。在使用日誌記錄裝飾器時,包裝器名稱始終是,因爲它是調用日誌的打印文件,所以它打印爲

順便說一句。日誌記錄。 級別()函數全部調用logging._log(*level*, ...),它也調用其他(日誌)函數。所有這些都在堆棧中結束。爲了防止顯示這些日誌功能,首先搜索幀列表中的第一個(最低)函數,該文件名不是'logging'的一部分。這應該是真正的日誌功能:一個呼叫記錄器。 func()。

遺憾的是,它是wrapper

但是,如果它是日誌記錄源文件的一部分,則可以使用日誌裝飾器:但還沒有(尚)

+0

所以我想這意味着Python 2.7.x系列不太可能解決這個問題,對吧? – kakyo

+0

它是開源的,所以做一個補丁!代碼在那裏,可讀性強,而且不復雜......而且據我記得2.7和3. *在這裏幾乎是一樣的。所以不要等3 – Albert

相關問題