2010-11-10 59 views
50

我使用的是標準的Python記錄模塊在我的Python應用程序:懶惰記錄消息字符串評估

 
import logging 
logging.basicConfig(level=logging.INFO) 
logger = logging.getLogger("log") 
while True: 
    logger.debug('Stupid log message " + ' '.join([str(i) for i in range(20)])) 
    # Do something 

的問題是,雖然調試級別不啓用,那個愚蠢的日誌消息是在每次循環迭代計算,這嚴重損害了性能。

有沒有解決方案?

在C++中,我們有log4cxx包,提供了這樣的宏:
LOG4CXX_DEBUG(logger, messasage)
這有效地評估爲

 
if (log4cxx::debugEnabled(logger)) { 
    log4cxx.log(logger,log4cxx::LOG4CXX_DEBUG, message) 
} 

但因爲在Python中沒有宏(據我所知),如果有一個有效的方法做日誌?

回答

61

日誌記錄模塊已經部分支持您想要執行的操作。這樣做:

log.debug("Some message: a=%s b=%s", a, b) 

,而不是這個...:

log.debug("Some message: a=%s b=%s" % (a, b)) 

記錄模塊是足夠聰明,除非該消息實際上得到的地方記錄到不能產生完整的日誌信息。

要將此功能應用於您的特定請求,您可以創建一個lazyjoin類。

class lazyjoin: 
    def __init__(self, s, items): 
     self.s = s 
     self.items = items 
    def __str__(self): 
     return self.s.join(self.items) 

使用方法如下(注意,使用一臺發電機的表情,增加了懶惰):

logger.info('Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20)))) 

這裏是一個演示,顯示這一工程。

>>> import logging 
>>> logging.basicConfig(level=logging.INFO) 
>>> logger = logging.getLogger("log") 
>>> class DoNotStr: 
...  def __str__(self): 
...   raise AssertionError("the code should not have called this") 
... 
>>> logger.info('Message %s', DoNotStr()) 
Traceback (most recent call last): 
... 
AssertionError: the code should not have called this 
>>> logger.debug('Message %s', DoNotStr()) 
>>> 

在演示中,該logger.info()調用命中斷言錯誤,而logger.debug()沒有走到這一步。

20
import logging 
import time 

logging.basicConfig(level=logging.INFO) 
logger = logging.getLogger("log") 

class Lazy(object): 
    def __init__(self,func): 
     self.func=func 
    def __str__(self): 
     return self.func() 

logger.debug(Lazy(lambda: time.sleep(20))) 

logger.info(Lazy(lambda: "Stupid log message " + ' '.join([str(i) for i in range(20)]))) 
# INFO:log:Stupid log message 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 

如果您運行腳本,你會注意到的第一logger.debug命令用不了20秒執行。這表明當日志記錄級別低於設置級別時,不會評估參數。

9

正如巴蒂爾指出,使用

log.debug("Some message: a=%s b=%s", a, b) 

...而不是這樣的:

log.debug("Some message: a=%s b=%s" % (a, b)) 

只執行字符串,如果實際記錄的消息格式可以節省一些時間。

這並不能完全解決問題,不過,因爲你可能要預先處理值格式化爲字符串,如:

log.debug("Some message: a=%s b=%s", foo.get_a(), foo.get_b()) 

在這種情況下,obj.get_a()obj.get_b()將計算如果沒有記錄發生,則爲,甚至

的解決方案,這將是使用lambda函數,但是這需要一些額外的機械:

class lazy_log_debug(object): 
    def __init__(self, func): 
     self.func = func 
     logging.debug("%s", self) 
    def __str__(self): 
     return self.func() 

...那麼你可以登錄以下各項:

lazy_log_debug(lambda: "Some message: a=%s b=%s" % (foo.get_a(), foo.get_b())) 

在這種情況下, ,如果log.debug決定執行格式化,則調用lambda函數僅調用,因此調用__str__方法。

請注意:該解決方案的開銷可能會超出收益:-)但至少在理論上,它可以完成懶惰的日誌記錄。

26

當然下面是​​不是作爲一個宏一樣高效:

if logger.isEnabledFor(logging.DEBUG): 
    logger.debug(
     'Stupid log message ' + ' '.join([str(i) for i in range(20)]) 
    ) 

,但簡單,evaluates in lazy fashion4倍,比接受的答案更快:

class lazyjoin: 
    def __init__(self, s, items): 
     self.s = s 
     self.items = items 

    def __str__(self): 
     return self.s.join(self.items) 

logger.debug(
    'Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20))) 
) 

benchmark-src我建立。

+1

簡單而高效。我喜歡。這應該可以獲得更多的讚譽。 – Rockallite 2014-09-16 13:26:53

+0

效率取決於手頭的情況,您應該始終以自己的方案爲基準。就我而言,懶惰的日誌記錄不需要任何參數,但可以在調用'__str__'時從類中收集東西。所以基本上,我得到了幾乎相同的結果。看到我的評論[這裏](https://gist.github.com/schnittstabil/9372911#gistcomment-1910973) – guyarad 2016-11-01 07:47:52

+0

@guyarad:你也需要花時間來創建'lazyjoin'實例。另請參閱我的答案[Python:如何做懶惰調試日誌記錄](http://stackoverflow.com/questions/21377020/python-how-to-do-lazy-debug-logging/22204021#22204021)。 – schnittstabil 2016-11-01 18:46:17