Python 日誌模塊logging分析及使用-2

本文做爲Python日誌模塊的補充,主要介紹日誌回滾RotatingFileHandlerTimedRotatingFileHandler的使用,以及其所帶來的問題、Logger對象的日誌等級是如何其做用的等內容。css

內容目錄

1、小總結2、Logger對象的日誌等級3、使用多個處理器和多種格式化4、日誌回滾1. RotatingFileHandler2. TimedRotatingFileHandler5、RotatingHandler存在的問題6、從多個進程記錄至單個文件html

1、小總結

經過前面介紹logging模塊的博文Python 日誌模塊 logging 分析及使用 - 掘金,基本上能夠正確使用日誌模塊。須要注意的幾點以下:python

  1. 直接使用logging模塊的接口函數,無任何其餘操做,如logging.info()來進行日誌的輸出是默認輸出到控制檯,默認的日誌等級是logging.WARNING,且不能夠設置日誌等級。
  2. 使用logging模塊的接口函數,內部實現是:判斷root.handlers是否爲空,爲空則內部調用basicConfig()函數,默認建立StreamHandler
  3. 使用logging.basicConfig()函數能夠知足基本使用,能夠輸出到文件或控制檯中。內部是根據參數來建立FileHandlerStreamHandler來實現。
  4. Logger不能夠直接實例化,須要使用logging.getLogger()獲取Logger對象。
  5. 一個logger對象能夠添加多個handler對象,經過addHandler()函數來添加。
  6. 每一個handler對象能夠有一個Formatter對象來指定格式,經過setFormatter()函數來設置。
  7. handlerlogger對象都須要設置一個日誌等級,經過setLevel()函數來設置。
  8. logger的名稱是一個以'.' 分割的層級結構,每一個'.'後面的logger都是'.'前面的loggerchildren
  9. 沒必要爲一個應用程序中所使用的全部loggers定義和配置handlers,只須要爲一個頂層的logger配置handlers,而後按照須要建立child loggers就足夠。
  10. logger有一個"有效等級(effective level)"的概念若是一個logger上沒有被明確設置level,那麼該logger就是使用它parentlevel;若是它的parent也沒有明確設置level則繼續向上查找parentparent的有效level,依次類推,直到找到個一個明確設置了level的祖先爲止。

2、Logger對象的日誌等級

由前文已知,Logger不能夠直接實例化,須要使用logging.getLogger(name)來獲取Logger的對象。經過setLevel()來設置日誌等級。web

在測試過程當中,發現了以下問題,設置了日誌等級爲logging.DEBUG,輸出Logger對象的level屬性,獲得的結果是10,但仍然不輸出DEBUG等級的信息,這是爲何呢?以下:c#

 1>>> import logging
2
3>>> logger = logging.getLogger('example')
4>>> logger.level
50
6>>> logger.debug('this is a debug msg.')# 無輸出
7>>> logger.warning('this is a warning msg.')
8this is a warning msg.
9>>> logger.setLevel(logging.DEBUG)
10>>> logger.level
1110
12>>> logger.debug('this is a debug msg.'# 無輸出
13>>> logger.warning('this is a warning msg.')
14this is a warning msg.
複製代碼

爲何設置了日誌等級而沒有起做用呢?-_-
首先分析一下,上面的代碼中獲取了Logger的對象logger,可是並無添加任何handler對象。當logging.getLogger()作了什麼呢?接口函數又作了哪些?安全

 1def getLogger(name=None):
2    """
3    Return a logger with the specified name, creating it if necessary.
4    If no name is specified, return the root logger.
5    """

6    if name:
7        return Logger.manager.getLogger(name)
8    else:
9        return root # 若未指定name,則返回root
10
11# root是什麼?
12root = RootLogger(WARNING) # RootLogger的對象,日誌等級爲WARNING
13Logger.root = root
14Logger.manager = Manager(Logger.root)
複製代碼

getLogger()函數內部根據是否指定name返回對應的root logger。即Logger的初始化對象,handler,filter參數等都爲None。可見默認的日誌等級是WARNING服務器

logger.info()接口函數爲例,看看又作了些什麼?app

函數調用關係:
Logger.info()調用self._log(INFO, msg, args, **kwargs)調用Logger._log()調用self.handle(record)調用self.callHandlers(record)
函數

接口函數調用關係
接口函數調用關係

分析下 Logger.callHandlers()函數:

1def callHandlers(self, record):
2    if (found == 0):
3        if lastResort:
4            if record.levelno >= lastResort.level:
5                lastResort.handle(record)
6        elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
7            sys.stderr.write("No handlers could be found for logger"
8                                 " \"%s\"\n" % self.name)
9            self.manager.emittedNoHandlerWarning = True
複製代碼
  1. 循環Logger本身實例,直到獲取到其祖輩,found來計數其handlers
  2. 判斷found個數,若是爲0,判斷lastResort

這個lastResort是什麼?以下:
它是_StderrHandler(WARNING)類的初始化對象,且默認傳遞的日誌等級是WARNING,沒法指定。
_StderrHandler類繼承自StreamHandler,使用sys.stderr相似於StreamHandlerpost

 1_defaultLastResort = _StderrHandler(WARNING) # 注意此處是WARNING
2lastResort = _defaultLastResort
3
4class _StderrHandler(StreamHandler):
5    """
6    This class is like a StreamHandler using sys.stderr, but always uses
7    whatever sys.stderr is currently set to rather than the value of
8    sys.stderr at handler construction time.
9    """

10    def __init__(self, level=NOTSET):
11        """
12        Initialize the handler.
13        """

14        Handler.__init__(self, level)
15
16    @property
17    def stream(self):
18        return sys.stderr
複製代碼

看到這裏已經明白了,之因此在獲取Logger對象,設置日誌等級後,依然沒有生效的緣由是,咱們沒有添加任何handler,程序內部默認調用指定等級爲WARNING_StderrHandler,且該日誌等級沒法修改(使用setLevel()不影響該handler對象的等級)

當咱們手動添加了handler對象後,則會調用添加的handler對象的等級或者root Logger的等級。

當調用logging.basicConfig()函數時,內部默認建立了FileHandlerStreamHandler對象,則咱們再設置setLevel()可生效。以下:

1import loging
2logging.basicConfig(level=logging.DEBUG)
3logger = logging.getLogger()
4logger.debug('this is a debug msg.')
5# 輸出
6INFO:root:this is a debug msg.
複製代碼

3、使用多個處理器和多種格式化

日誌記錄器是普通的Python對象。addHandler()方法沒有限制能夠添加的日誌處理器數量。有時候,應用程序須要將嚴重類的消息記錄在一個文本文件,而將錯誤類或其餘等級的消息輸出在控制檯中。要進行這樣的設定,只需多配置幾個日誌處理器便可,在應用程序代碼中的日誌記錄調用能夠保持不變。

 1import logging
2
3logger = logging.getLogger('simple_example')
4logger.setLevel(logging.DEBUG)
5# 建立文件handler ,其等級爲debug
6fh = logging.FileHandler('example.log')
7fh.setLevel(logging.DEBUG)
8# 建立控制檯handler,日誌等級爲ERROR
9ch = logging.StreamHandler()
10ch.setLevel(logging.ERROR)
11# 建立formatter並添加至handlers
12formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
13ch.setFormatter(formatter)
14fh.setFormatter(formatter)
15# 將handlers添加至logger
16logger.addHandler(ch)
17logger.addHandler(fh)
18
19# 應用代碼
20logger.debug('debug message')
21logger.info('info message')
22logger.warning('warn message')
23logger.error('error message')
24logger.critical('critical message')
複製代碼

上述代碼的結果是,控制檯只輸出errorcritical的日誌信息,而文件中則包含全部5個日誌信息。

4、日誌回滾

經過前面的分析,咱們能夠將日誌信息輸出到一個文件中,隨着時間的流逝,日誌文件會變得愈來愈大,如何處理這種狀況?

咱們但願當日志文件不斷記錄增加至必定大小或增加到必定時間時,打開一個新的文件接着記錄。你可能但願只保留必定數量的日誌文件,當不斷的建立文件到達該數量時,又覆蓋掉最開始的文件造成循環。 對於這種使用場景,日誌包提供了 logging.hanlders.RotatingFileHandlerlogging.hanlders.TimedRotatingFileHandler

在上篇文章中講到過:

描述
logging.handlers.RotatingFileHandler 將日誌消息發送到磁盤文件,並支持日誌文件按大小切割
logging.hanlders.TimedRotatingFileHandler 將日誌消息發送到磁盤文件,並支持日誌文件按時間切割

1. RotatingFileHandler

默認狀況下,文件會無限增加。能夠指定maxBytesbackupCount的特定值,以容許文件以預約的大小滾動。

噹噹前日誌文件的長度接近maxBytes時,就會發生翻轉。若是backupCount爲>= 1,則系統將連續建立與基本文件路徑名相同、但具備擴展名的新文件".1"".2"等附於其後。

例如,若是backupCount5,而且基本文件名爲「app.log」,則會獲得「app.log」「app.log.1」「app.log.2」,…,「app.log.5」

被寫入的文件老是「app.log」

當它被填滿時,它被關閉並重命名爲「app.log.1」,若是文件「app.log.1」「app.log.2」等存在,而後將它們重命名爲「app.log.2」「app.log.3」等。

  • 若是maxBytes爲零,則不會發生翻轉。
  • 若是想要翻轉功能,則mode='a'

初始化函數定義:

1__init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)
複製代碼

初始化參數:

參數名 含義
filename 文件名
mode 文件模式。使用回滾功能時,設置爲a
maxBytes 文件大小,最大比特數,如1024*1024*1024表示一個G
backupCount 文件回滾個數,如設爲3,則會保留3個備份文件,一共4個日誌文件
encoding 文件編碼格式,若是包含中文,則使用utf-8編碼
delay 構建Handler,用來設置等級,格式等項。若爲True,構建Handler對象,不然構建StreamHandler對象。默認False

2. TimedRotatingFileHandler

參數when決定了時間間隔的類型,參數interval決定了多少的時間間隔。如when=‘D’,interval=2,就是指兩天的時間間隔,backupCount決定了能留幾個日誌文件。超過數量就會丟棄掉老的日誌文件。

初始化函數定義

1__init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)
複製代碼

初始化參數:

參數名 含義
filename 文件名
when 時間間隔的類型
interval 時間間隔,
backupCount 文件回滾個數,如設爲3,則會保留3個備份文件,一共4個日誌文件
encoding 文件編碼格式,若是包含中文,則使用utf-8編碼
delay 構建Handler,用來設置等級,格式等項。若爲True,構建Handler對象,不然構建StreamHandler對象。默認False
utc UTC時區的時間

參數when:

符號 含義
S
M 分鐘
H 小時
D
W
W0-W6 週一到週日
midnight 在午夜,即天天凌晨

示例:

 1import glob
2import logging
3import logging.handlers
4
5my_logger = logging.getLogger('MyLogger')
6my_logger.setLevel(logging.DEBUG)
7
8# Add the log message handler to the logger
9handler = logging.handlers.RotatingFileHandler(
10              'logs/logging_demo.log', maxBytes=1024*1024, backupCount=5)
11
12my_logger.addHandler(handler)
複製代碼

獲得的日誌文件以下,共6個文件:

1logging_demo.log
2logging_demo.log.1
3logging_demo.log.2
4logging_demo.log.3
5logging_demo.log.4
6logging_demo.log.5
複製代碼

5、RotatingHandler存在的問題

Python 的logging模塊提供了兩個支持日誌回滾的FileHandler類,分別是RotatingFileHandlerTimedRotatingFileHandler

logging是線程安全的,將單個進程中的多個線程日誌記錄至單個文件沒有問題。但當有多個進程向同一個日誌文件寫入日誌的時候,這兩個RotatingHandler就會帶來問題,好比日誌丟失。

具體可參考如下博文:
Python TimedRotatingFileHandler 多進程環境下的問題和解決方法 - 喵醬的書架 - OSCHINA

6、從多個進程記錄至單個文件

logging是線程安全的,將單個進程中的多個線程日誌記錄至單個文件也是受支持的;但將多個進程中的日誌記錄至單個文件則不受支持,由於在Python中並無在多個進程中實現對單個文件訪問的序列化的標準方案。

若是你須要將多個進程中的日誌記錄至單個文件,有一個方案是讓全部進程都將日誌記錄至一個 SocketHandler,而後用一個實現了套接字服務器的單獨進程一邊從套接字中讀取一邊將日誌記錄至文件。(若是願意的話,你能夠在一個現有進程中專門開一個線程來執行此項功能。) 這一部分文檔對此方式有更詳細的介紹,幷包含一個可用的套接字接收器,你本身的應用能夠在此基礎上進行適配。

這部份內容還未細看。

相關文章
相關標籤/搜索