Python學習——logging模組

淺洛帆發表於2018-08-13

一、概述

好的程式開發,往往會兼顧到日誌輸出的需求,以便給使用者提供必要的日誌資訊便於確認程式執行狀態、排錯等等。這些日誌一般包括程式的正常執行日誌、訪問日誌、錯誤日誌、資料儲存日誌等型別。在python中logging模組提供了標準的日誌介面,可以滿足我們對日誌輸出的各種需求,下面一一詳述。

二、logging模組入門

2.1 日誌級別

業內常用的日誌有五個級別,分別是:debug,info,warning,error和critical,其中debug級別最低,critical級別最高,級別越低,列印的日誌等級越多。各個級別的定義如下:

Level When it’s used
DEBUG Detailed information, typically of interest only when diagnosing problems.
INFO Confirmation that things are working as expected.
WARNING An indication that something unexpected happened, or indicative of some problem in the near future(e.g. ‘disk spack low’). The software is still working as expected.
ERROR Due to a more serious problem, the software has not been able to perform some function.
CRITICAL A serious error, indicating that the program itself may be unable continue running.

預設的級別是warning,只有在warning級別和以上級別的日誌才會輸出,這樣可以避免日誌過多的問題。當然這個預設的級別是可以自定義修改的,下文詳述。

2.2 標準日誌輸出

最簡單的日誌輸出做輸出到stdout,看程式碼:

import logging

logging.debug('[debug]')
logging.info('[info]')
logging.warning('[warning]')
logging.error('[error]')
logging.critical('[critical]')

輸出:
WARNING:root:[warning]
ERROR:root:[error]
CRITICAL:root:[critical]

# 預設的日誌級別是warning,該級別以下的日誌沒有輸出。

2.3 日誌輸出到檔案

日誌輸出到檔案具有持久化儲存的功能,便於隨時回溯檢視。通過basicConfig可以定義日誌輸出的引數。關鍵引數如下:

關鍵字 描述
filename 建立一個FileHandler,使用指定的檔名,而不是使用StreamHandler。
filemode 如果指明瞭檔名,指明開啟檔案的模式(如果沒有指明filemode,預設為‘a’)
format handler使用指明的格式化字串
datefmt 使用指明的日期/時間格式
level 指明根logger的級別
stream 使用指明的流來初始化StreamHandler。該引數與‘filename’不相容,如果兩個都有,‘stream’被忽略。

當然了,這個日誌級別是可以靈活自定義的。

import logging

logging.basicConfig(filename='running.log', level=logging.INFO)
logging.debug('[debug]')
logging.info('[info]')
logging.warning('[warning]')
logging.error('[error]')
logging.critical('[critical]')

日誌檔案輸出結果:
這裡寫圖片描述

2.4 format格式化輸出日誌

前面的示例中,日誌的輸出沒有什麼可讀性而言,這裡可以通過format來增加時間等欄位,提高可讀性。常用的format相關的引數如下:

引數 說明
%(levelno)s 數字形式的日誌級別
%(levelname)s 文字形式的日誌級別
%(pathname)s 呼叫日誌輸出函式的模組的完整路徑名,可能沒有
%(filename)s 呼叫日誌輸出函式的模組的檔名
%(module)s 呼叫日誌輸出函式的模組名
%(funcName)s 呼叫日誌輸出函式的函式名
%(lineno)d 呼叫日誌輸出函式的語句所在的程式碼行
%(created)f 當前時間,用UNIX標準的表示時間的浮點數表示
%(relativeCreated)d 輸出日誌資訊時的,自Logger建立以來的毫秒數
%(asctime)s 字串形式的當前時間。預設格式是“2018-04-25 10:39:50, 896”。逗號後面的是毫秒
%(thread)d 執行緒ID。可能沒有
%(threadName)s 執行緒名。可能沒有
%(process)d 程式ID。可能沒有
%(message)s 使用者輸出的訊息
import logging

logging.basicConfig(filename='running.log', level=logging.INFO, format='%(asctime)s [%(levelname)s] %(module)s %(message)s')
logging.debug('[debug]')
logging.info('service started')
logging.warning('memory leak')
logging.error('can not started')
logging.critical('fatal error')

程式輸出:
2018-04-25 10:54:22,479 [INFO] p_logging service started
2018-04-25 10:54:22,479 [WARNING] p_logging memory leak
2018-04-25 10:54:22,479 [ERROR] p_logging can not started
2018-04-25 10:54:22,479 [CRITICAL] p_logging fatal error

因為示例程式的檔名為p_logging.py,所以這裡日誌輸出的模組名顯示為p_logging。還可以通datefmt引數自定義日期時間輸出格式:

import logging

logging.basicConfig(filename='running.log', level=logging.INFO, format='%(asctime)s [%(levelname)s] %(module)s %(message)s',datefmt='%m/%d/%Y %H:%M:%S')
logging.debug('[debug]')
logging.info('service started')
logging.warning('memory leak')
logging.error('can not started')
logging.critical('fatal error')

輸出:
04/25/2018 10:56:41 [INFO] p_logging service started
04/25/2018 10:56:41 [WARNING] p_logging memory leak
04/25/2018 10:56:41 [ERROR] p_logging can not started
04/25/2018 10:56:41 [CRITICAL] p_logging fatal error

三、logging模組進階

前文講述了日誌輸出的一些基本用法,複雜的用法還需要進階學習:

3.1 logging模組的主要類概述

python使用logging模組記錄日誌涉及四個主要類:

  • logger:記錄器,提供了應用程式可以直接使用的介面。

  • handler:處理器,將(logger建立的)日誌記錄傳送到合適的目的輸出。必須有handler才能捕獲輸出的日誌。

  • filter:過濾器,對日誌進行過濾來決定輸出哪條日誌記錄。

  • formatter:格式化器,決定日誌記錄的最終輸出格式。

3.2 logger詳解

Logger是一個樹形層級結構,在使用介面debug,info,warn,error,critical之前必須建立Logger例項,即建立一個記錄器,如果沒有顯式的進行建立,則預設建立一個root logger,並應用預設的日誌級別(WARN),處理器Handler(StreamHandler,即將日誌資訊列印輸出在標準輸出上),和格式化器Formatter(預設的格式即為第一個簡單使用程式中輸出的格式)。

建立方法: logger = logging.getLogger(logger_name)

建立Logger例項後,可以使用以下方法進行相關設定:

  • logger.setLevel(logging.ERROR) # 設定日誌級別為ERROR,即只有日誌級別大於等於ERROR的日誌才會輸出;

  • logger.addHandler(handler_name) # 為Logger例項增加一個處理器;

  • logger.removeHandler(handler_name) # 為Logger例項刪除一個處理器;

  • logger.addFilter(filt) #為logger例項增加過濾器;

  • logger.removeFilter(filt) #為logger例項刪除過濾器;

  • logger.debug()、logger.info()、logger.warning()、logger.error()、logger.critical() #設定指定級別的日誌輸出;

  • len(logger.handlers) #獲取handler的個數。

3.3 handler詳解

handler物件負責傳送相關的資訊到指定目的地。Python的日誌系統有多種Handler可以使用。有些Handler可以把資訊輸出到控制檯,有些Logger可以把資訊輸出到檔案,還有些 Handler可以把資訊傳送到網路上。如果覺得不夠用,還可以編寫自己的Handler。可以通過addHandler()方法新增多個多handler 。

Handler處理器型別有很多種,比較常用的有三個,StreamHandler,FileHandler,NullHandler,詳情可以訪問Python logging.handlers
建立StreamHandler之後,可以通過使用以下方法設定日誌級別,設定格式化器Formatter,增加或刪除過濾器Filter。

  • ch.setLevel(logging.WARN) # 指定日誌級別,低於WARN級別的日誌將被忽略;
  • ch.setFormatter(formatter_name) # 設定一個格式化器formatter;
  • ch.addFilter(filter_name) # 增加一個過濾器,可以增加多個;
  • ch.removeFilter(filter_name) # 刪除一個過濾器;

StreamHandler

建立方法: sh = logging.StreamHandler(stream=None)

FileHandler

建立方法: fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False)

NullHandler

NullHandler類位於核心logging包,不做任何的格式化或者輸出。
本質上它是個“什麼都不做”的handler,由庫開發者使用。
下面講解handler的重要知識點:

  • logging.StreamHandler
    說明:建立一個handler物件,使用這個Handler可以向類似與sys.stdout或者sys.stderr的任何檔案物件(file object)輸出資訊,也就是螢幕輸出。
    它的建構函式是:StreamHandler([strm]),其中strm引數是一個檔案物件,預設是sys.stderr。
import logging
logger = logging.getLogger('testlog')  # 建立一個logger物件
logger.setLevel(logging.DEBUG)  # 設定logger的日誌級別
stdout = logging.StreamHandler()  # 建立一個handler物件,日誌輸出到stdout
stdout.setLevel(logging.INFO)  # 設定handler的日誌級別
logger.addHandler(stdout)  # 為logger增加指定的handler
logger.debug('debug log')  # 輸出日誌
logger.info('info log')  # 輸出日誌

輸出:
info log

上述示例程式中,由於handler中設定的日誌級別是info,比logger中的debug還要高,因此結果輸出中僅僅輸出了info級別的日誌。

  • logging.FileHandler
    說明:和StreamHandler類似,用於向一個檔案輸出日誌資訊,不過FileHandler會幫你開啟這個檔案。
    它的建構函式是:FileHandler(filename[,mode])。filename是檔名,必須指定一個檔名。mode是檔案的開啟方式。參見Python內建函式open()的用法。預設是’a’,即新增到檔案末尾。
import logging

logger = logging.getLogger('testlog')
logger.setLevel(logging.DEBUG)

fh = logging.FileHandler('test.log')  #建立一個fileHandler物件,預設追加寫
fh.setLevel(logging.INFO) #設定handler的日誌級別

formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
fh.setFormatter(formatter) #為handler設定格式化器

logger.addHandler(fh)
logger.debug('debug log')
logger.info('info log')
logger.error('error log')

輸出test.log的內容:
2018-04-25 11:53:28,231 [INFO] info log --p_logging
2018-04-25 11:53:28,232 [ERROR] error log --p_logging
  • logging.handlers.RotatingFileHandler
    說明:這個Handler類似於上面的FileHandler,但是它可以管理檔案大小。當檔案達到一定大小(位元組數)之後,它會自動進行日誌檔案滾動切割:將當前日誌檔案改名,然後建立 一個新的同名日誌檔案繼續輸出。比如日誌檔案是chat.log。當chat.log達到指定的大小之後,RotatingFileHandler自動把 檔案改名為chat.log.1。不過,如果chat.log.1已經存在,會先把chat.log.1重新命名為chat.log.2。。。最後重新建立 chat.log,繼續輸出日誌資訊。

它的建構函式是:RotatingFileHandler( filename, mode, maxBytes, backupCount),其中filename和mode兩個引數和FileHandler一樣。
maxBytes用於指定日誌檔案的最大檔案大小。如果maxBytes為0,意味著日誌檔案可以無限大,這時上面描述的重新命名過程就不會發生。
backupCount用於指定保留的備份檔案的個數。比如,如果指定為2,當上面描述的重新命名過程發生時,原有的chat.log.2並不會被更名,而是被刪除。

import logging
from logging import handlers

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

#單個日誌檔案最大50位元組,最多保留3個檔案
fh = logging.handlers.RotatingFileHandler(filename='time.log', maxBytes=50, backupCount=3)
fh.setLevel(logging.INFO)

#設定formatter
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
fh.setFormatter(formatter)

logger.addHandler(fh)
logger.debug('debug log')
logger.info('info log')
logger.error('error log')

輸出:
time.log.1 檔案   2018-04-25 11:08:26,253 [INFO] info log --log
time.log 檔案 2018-04-25 11:08:26,254 [ERROR] error log --log

上述示例程式中由於一行日誌的輸出剛好達到50位元組的日誌檔案切割臨界值,所以兩行log輸出到了兩個日誌檔案中,且越早輸出的日誌會寫到了字尾越大的日誌檔案中(最新的日誌檔案,可認為字尾為0)

  • logging.handlers.TimedRotatingFileHandler
    說明:這個Handler和RotatingFileHandler類似,不過,它沒有通過判斷檔案大小來決定何時重新建立日誌檔案,而是間隔一定時間就自動建立新的日誌檔案。重新命名的過程與RotatingFileHandler類似,不過新的檔案不是附加數字,而是當前時間。
    它的建構函式是:TimedRotatingFileHandler( filename,when,interval,backupCount),其中filename引數和backupCount引數和RotatingFileHandler具有相同的意義。

    interval是時間間隔。
    when引數是一個字串。表示時間間隔的單位,不區分大小寫。它有以下取值:①S:秒②M:分③H:小時④D:天⑤W :每星期(interval==0時代表星期一)⑥midnight:每天凌晨

import logging
from logging import handlers
import time

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

#每隔5秒切割一次日誌檔案,最多保留3份
fh = logging.handlers.TimedRotatingFileHandler(filename='time.log', when='S', interval=3, backupCount=3)
fh.setLevel(logging.INFO)

formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s --%(module)s')
fh.setFormatter(formatter)

logger.addHandler(fh)
logger.debug('debug log')
time.sleep(3)
logger.info('info log')
time.sleep(3)
logger.error('error log')

輸出:
time.log                2018-02-21 11:18:05,867 [ERROR] error log --log
time.log.2018-02-21_11-18-02    2018-02-21 11:18:02,842 [INFO] info log --log
time.log.2018-02-21_11-17-59        檔案為空,但因為有sleep導致達到日誌滾動的時間閾值,所以滾動生成了一個空日誌檔案

3.4 Formatter 格式化器

使用Formatter物件設定日誌資訊最後的規則、結構和內容,預設的時間格式為%Y-%m-%d %H:%M:%S。

建立方法: formatter = logging.Formatter(fmt=None, datefmt=None)

其中,fmt是訊息的格式化字串,datefmt是日期字串。如果不指明fmt,將使用’%(message)s’。如果不指明datefmt,將使用ISO8601日期格式。

3.5 filter 過濾器

Handlers和Loggers可以使用Filters來完成比級別更復雜的過濾。Filter基類只允許特定Logger層次以下的事件。例如用‘A.B’初始化的Filter允許Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等記錄的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字串來初始化,所有的事件都接受。

建立方法: filter = logging.Filter(name='')

filter的示例程式待後續補充更新。

四、綜合運用–把日誌同時輸出到標準輸出和日誌檔案

思路比較簡單了,先後建立StreamHandler和FileHandler,定義各自的level、formatter,最後add到logger即可。

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

formater = logging.Formatter('%(asctime)s  [%(levelname)s] %(name)s %(message)s ---%(module)s')

sh = logging.StreamHandler()
sh.setLevel(logging.WARN)
sh.setFormatter(formater)

fh = logging.FileHandler('test.log')
fh.setLevel(logging.WARN)
fh.setFormatter(formater)

logger.addHandler(sh)
logger.addHandler(fh)

logger.debug('debug log')
logger.info('info log')
logger.error('error log')

輸出:
2018-02-21 11:39:38,398  [ERROR] __main__ error log ---log

五、概念總結

以下是相關概念總結:

熟悉了這些概念之後,有另外一個比較重要的事情必須清楚,即Logger是一個樹形層級結構;
Logger可以包含一個或多個Handler和Filter,即Logger與Handler或Fitler是一對多的關係;
一個Logger例項可以新增多個Handler,一個Handler可以新增多個格式化器或多個過濾器,而且日誌級別將會繼承,但Handler中的日誌級別會覆蓋Logger中的日誌級別設定
這裡寫圖片描述
參考:http://www.cnblogs.com/linupython/p/8456431.html

相關文章