天天看點

Python子產品之logging

作為一個程式員,個人認為一個好的日志記錄習慣是能稱得上優秀所必備的素養之一。而且随着在行業内閱曆的增長,這個好習慣在心目中的比重越來越高。

0. 日志的重要性

為了凸顯log的重要程度,筆者不惜打亂已有的行文風格——在開始本篇标題辨別的内容前,用一個比較典型的場景來說明下日志為何如此重要。

碰到QA提的一個bug的時候,筆者見識過兩種方式的答複:

a) 請給我重制步驟和重制資料;

b) 把當時的日志給我。

答複前者的,一般需要花很多時間去找問題出現在那裡,如果是别人開發的子產品的話,花費的時間更多。而答複後者的,一般能很快的找到出問題的點,然後就可以開始進入修複的流程。(希望讀者你已經是,或者有志并願意努力成為後者)

從概念上來說:日志是一個可運作的、可維護的軟體的基礎組成部分;通過日志,我們可以了解軟體系統在運作中的實時狀态,曆史狀态和異常狀态等。一個沒有良好日志的軟體是所有人的噩夢。

1. 概述

相比較Java,.NET等語言龐大的日志系統,第三方架構層出不窮的現象,python在這一方面表現得倒是出奇地統一,網上找到的資料讨論的都是Python内置的logging子產品,這真是選擇強迫症的救星。本文也無法免俗,本篇部落格沒有啥原理性和深入性的知識的探索,僅僅是記錄在使用過程中的一些問題,提供一份快速上手的Demo Code。

2. 實作

2.1 配置檔案

以下配置存儲為

ogger_config.conf

檔案,注意該配置檔案中,筆者盡量減少了進階知識的引入,以期最大限度地減少初學者的心理和生理壓力,讀者可以在完成初步探索之後,在需求的驅動下自行進行擴充,學習是一個循序漸進的過程,慢慢來,不要着急。

在下面的配置中,我們隻引入了一個logger,我們在程式中列印的日志都是通過它來進行輸出的,并且提供了兩種handler(console,file)。

[loggers]
keys=root

[logger_root]
level=DEBUG
# ,filehandler
handlers=consoleHandler

###############################################
[handlers]
keys=consoleHandler,filehandler

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[handler_filehandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('./ogs/debug.log', 'a')

###############################################
[formatters]
keys=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S

           
2.2 代碼實作
import logging
import logging.config
from os import path

def _init_logger():
    log_file_path = path.join(path.dirname(path.abspath(__file__)), 'logger_config.conf')
    logging.config.fileConfig(log_file_path)

def _log_test():
    logger = logging.getLogger(__name__)
    logger.debug("LQ")

if __name__ == '__main__':
	_init_logger()
	_log_test()
           

3. 最佳實踐

任何一門知識點必然有一些最佳實踐,提前知曉它們并努力去适應永遠是一個利遠大于弊的選擇。

3.1 使用

__name__

作為日志名稱(logger name)

這裡先舉個例子,假設你在

foo.bar.my_module

子產品中調用

logger.getLogger(__name__)

,即是調用了

logger.getLogger("foo.bar.my_module")

;這樣做的好處至少有兩個:

  1. 這樣當你需要為logger添加配置時,你就隻需要給"foo"添加配置,然後所有在"foo"裡的子產品都能共享相同的配置資訊。
  2. 你在檢視日志時,可以非常清晰地了解到某條日志資訊是從哪個子產品裡輸出的。
3.2 記錄異常時記得帶上堆棧資訊

這一條不僅僅适用于Python;隻要是有日志記錄功能的地方,這一條都可以算得上是鐵律了;我們這裡重點看下Python中如何實作這一點:

try:
        open('/path/to/does/not/exist', 'rb')
    except (SystemExit, KeyboardInterrupt):
        raise
    except Exception as e:
    	# 通過将exc_info參數設定為True調用logger的相關方法,traceback将會被記錄進logger
        # 當然你也可以使用 logger.exception(msg, *args)方法
        logger.error('Failed to open file', exc_info=True)
           
3.3 在需要的時候執行個體化logger
import logging
# 在函數内部使用
def foo():
	logger = logging.getLogger(__name__)
	logger.info('Hi, foo')

# 作為類的成員
class Bar(object):
	def __init__(self, logger=None):
		self.logger = logger or logging.getLogger(__name__)
	def bar(self):
		self.logger.info('Hi, bar')

           

4. 總結

Python中的logging是被作為标準庫的一部分的,靈活性也非常高。而且正如Python一直所宣稱的,logging的使用上也是相當地簡單。

但是log的難點永遠不是在其使用上,如何制定符合業務發展需要并且被廣大開發人員接受并執行的日志規範,以及後期日志資訊的提取和分析才是有關log話題的精華所在。

5. Links

  1. 最佳實踐 – 2013年的時候找到的,沒想到還有再次打開的一天