Print不用的好好的麼,為什麼要用logging?因為調試時,print語句需要改動代碼,調試完你還得刪除掉,萬一忘記怎麼辦?還有線上代碼運行時你想知道當時發生了什麼,logging是最好的辦法。
先看下logging的簡單用法:
import logginglogging.basicConfig(level=logging.DEBUG)logger = logging.getLogger(__name__)logger.info('Start logging')msg = 'test'logger.debug('message: %s', msg)logger.info('End logging')
logging主要由以下模塊組成:
Loggers: 可供程序直接調用的接口。可以直接向logger寫入日誌信息。logger並不是直接實例化使用的,而是通過logging.getLogger(name)來獲取對象,事實上logger對象是單例模式,logging是多線程安全的,也就是無論程序中哪裡需要打日誌獲取到的logger對象都是同一個。
每個logger都有一個日誌的級別。logging中定義了如下級別:
LevelNumeric valueCRITICAL50ERROR40WARNING30INFO20DEBUG10NOTSET0那麼什麼時候用何種界別的log呢?
LevelWhen it’s usedDEBUGDetailed information, typically of interest only when diagnosing problems.INFOConfirmation that things are working as expected.WARNINGAn indication that something unexpected happened, or indicative of some problem in the near future (e.g. 『disk space low』). The software is still working as expected.ERRORDue to a more serious problem, the software has not been able to perform some function.CRITICALA serious error, indicating that the program itself may be unable to continue running.當一個logger收到日誌信息後先判斷是否符合level,如果決定要處理就將信息傳遞給Handlers進行處理。
Handlers: 決定將日誌記錄分配至正確的目的地
將logger發過來的信息進行準確地分配,送往正確的地方。舉個慄子,送往控制臺或者文件或者both或者其他地方(進程管道之類的)。它決定了每個日誌的行為,是之後需要配置的重點區域。
每個Handler同樣有一個日誌級別,一個logger可以擁有多個handler也就是說logger可以根據不同的日誌級別將日誌傳遞給不同的handler。當然也可以相同的級別傳遞給多個handlers這就根據需求來靈活的設置了
Filters: 提供更細粒度的日誌是否輸出的判斷來決定日誌是否需要列印。原則上handler獲得一個日誌就必定會根據級別被統一處理,但是如果handler擁有一個Filter可以對日誌進行額外的處理和判斷。例如Filter能夠對來自特定源的日誌進行攔截or修改甚至修改其日誌級別(修改後再進行級別判斷)。
logger和handler都可以安裝filter甚至可以安裝多個filter串聯起來。
Formatters: 制定最終記錄列印的格式布局。Formatter會將傳遞來的信息拼接成一條具體的字符串,默認情況下Format只會將信息%(message)s直接列印出來。Format中有一些自帶的LogRecord屬性可以使用,如下表格:
Attribute nameFormatDescriptionasctime%(asctime)sHuman-readable time when the LogRecord was created. By default this is of the form 『2003-07-08 16:49:45,896』 (the numbers after the comma are millisecond portion of the time).created%(created)fTime when the LogRecord was created (as returned by time.time()).filename%(filename)sFilename portion of pathname.funcName%(funcName)sName of function containing the logging call.levelname%(levelname)sText logging level for the message ('DEBUG', 'INFO', 'WARNING','ERROR', 'CRITICAL').levelno%(levelno)sNumeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL).lineno%(lineno)dSource line number where the logging call was issued (if available).module%(module)sModule (name portion of filename).msecs%(msecs)dMillisecond portion of the time when the LogRecord was created.message%(message)sThe logged message, computed as msg % args. This is set whenFormatter.format() is invoked.name%(name)sName of the logger used to log the call.pathname%(pathname)sFull pathname of the source file where the logging call was issued (if available).process%(process)dProcess ID (if available).processName%(processName)sProcess name (if available).relativeCreated%(relativeCreated)dTime in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.thread%(thread)dThread ID (if available).threadName%(threadName)sThread name (if available).一個Handler只能擁有一個Formatter 因此如果要實現多種格式的輸出只能用多個Handler來實現。
如何使用logging模塊呢?
1. 定義formatter——決定輸出格式
2. 定義handler——決定輸出位置(console或者文件)
3. 定義logger——最終程序調用的日誌接口
4. 將formatter綁定到handler上。
5. 將handler綁定至logger上
這樣就可以使用logger對象進行輸出日誌了。舉例如下:
#encoding: UTF-8import multiprocessingimport loggingimport randomclass LogHandler(object): def __init__(self, log_level, log_file): self.log_name = __name__ self.log_level = log_level.upper() self.log_file = log_file def get_logger(self): # 1.定義handler的輸出格式(formatter) formatter = logging.Formatter('%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s') # 2. 創建一個handler,用於寫入日誌文件 fh = logging.FileHandler(self.log_file) fh.setLevel(self.log_level) # 再創建一個handler,用於輸出到控制臺 ch = logging.StreamHandler() ch.setLevel(self.log_level) # 3. 創建一個logger logger = logging.getLogger(self.log_name) logger.setLevel(self.log_level) # 4. 綁定formatter到handle上 fh.setFormatter(formatter) ch.setFormatter(formatter) # 5.綁定handler到logger上 logger.addHandler(fh) logger.addHandler(ch) return loggerdef func(): log_handle.info("test starts") log_handle.debug("111") product = random.randint(0, 10) msg = "test finished %s" %product log_handle.warn(msg) try: open("c:\notexist.rt") except: log_handle.error('af', exc_info=True)if __name__ == "__main__": log_handle = LogHandler( "info", r"c:\1.txt").get_logger() func()
我們看下輸出:
2016-12-01 16:19:58,017 - __main__ - func - INFO - test starts
2016-12-01 16:19:58,019 - __main__ - func - WARNING - test finished 0
2016-12-01 16:19:58,019 - __main__ - func - ERROR - af
Traceback (most recent call last):
File "E:/qa_report_auto_generator/l2.py", line 47, in func
open("c:\notexist.rt")
IOError: [Errno 22] invalid mode ('r') or filename: 'c:\notexist.rt'
因為我們log level 設置的是info,所以看到紅色部分debug未列印出,黃色高亮部分是為了把traceback信息dump到logger裡, 這個很有用處。
__init__self.log_name = __name__ 這句話是把當前的module名作為log名字。
舉例來說,如果你把func函數放到另外一個test.pyfile裡,那麼引用它列印出來的log name就
是test.
歡迎關注 iTesting.
iTesting致力於軟體測試技術分享, 包括不限於 Web測試, Mobile測試, API測試, 性能測試以及測試職位推薦.
長按下圖二維碼,在彈出菜單中選擇「識別圖中二維碼」關注本公眾號.
更多內容,敬請期待