本文做爲Python日誌模塊的補充,主要介紹日誌回滾
RotatingFileHandler
和TimedRotatingFileHandler
的使用,以及其所帶來的問題、Logger
對象的日誌等級是如何其做用的等內容。css
1、小總結2、Logger對象的日誌等級3、使用多個處理器和多種格式化4、日誌回滾1. RotatingFileHandler2. TimedRotatingFileHandler5、RotatingHandler存在的問題6、從多個進程記錄至單個文件html
經過前面介紹logging
模塊的博文Python 日誌模塊 logging 分析及使用 - 掘金,基本上能夠正確使用日誌模塊。須要注意的幾點以下:python
logging
模塊的接口函數,無任何其餘操做,如logging.info()
來進行日誌的輸出是默認輸出到控制檯,默認的日誌等級是logging.WARNING
,且不能夠設置日誌等級。logging
模塊的接口函數,內部實現是:判斷root.handlers
是否爲空,爲空則內部調用basicConfig()
函數,默認建立StreamHandler
。logging.basicConfig()
函數能夠知足基本使用,能夠輸出到文件或控制檯中。內部是根據參數來建立FileHandler
或StreamHandler
來實現。Logger
不能夠直接實例化,須要使用logging.getLogger()
獲取Logger
對象。logger
對象能夠添加多個handler
對象,經過addHandler()
函數來添加。handler
對象能夠有一個Formatter
對象來指定格式,經過setFormatter()
函數來設置。handler
和logger
對象都須要設置一個日誌等級,經過setLevel()
函數來設置。logger
的名稱是一個以'.'
分割的層級結構,每一個'.'
後面的logger
都是'.'
前面的logger
的children
。loggers
定義和配置handlers
,只須要爲一個頂層的logger
配置handlers
,而後按照須要建立child loggers
就足夠。logger
有一個"有效等級(effective level)"的概念若是一個logger
上沒有被明確設置level
,那麼該logger
就是使用它parent
的level
;若是它的parent
也沒有明確設置level
則繼續向上查找parent
的parent
的有效level
,依次類推,直到找到個一個明確設置了level
的祖先爲止。由前文已知,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
複製代碼
Logger
本身實例,直到獲取到其祖輩,found
來計數其handlers
。found
個數,若是爲0,判斷lastResort
這個lastResort
是什麼?以下:
它是_StderrHandler(WARNING)
類的初始化對象,且默認傳遞的日誌等級是WARNING
,沒法指定。_StderrHandler
類繼承自StreamHandler
,使用sys.stderr
相似於StreamHandler
。post
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()
函數時,內部默認建立了FileHandler
或StreamHandler
對象,則咱們再設置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.
複製代碼
日誌記錄器是普通的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')
複製代碼
上述代碼的結果是,控制檯只輸出error
和critical
的日誌信息,而文件中則包含全部5個日誌信息。
經過前面的分析,咱們能夠將日誌信息輸出到一個文件中,隨着時間的流逝,日誌文件會變得愈來愈大,如何處理這種狀況?
咱們但願當日志文件不斷記錄增加至必定大小或增加到必定時間時,打開一個新的文件接着記錄。你可能但願只保留必定數量的日誌文件,當不斷的建立文件到達該數量時,又覆蓋掉最開始的文件造成循環。 對於這種使用場景,日誌包提供了 logging.hanlders.RotatingFileHandler
和logging.hanlders.TimedRotatingFileHandler
。
在上篇文章中講到過:
類 | 描述 |
---|---|
logging.handlers.RotatingFileHandler | 將日誌消息發送到磁盤文件,並支持日誌文件按大小切割 |
logging.hanlders.TimedRotatingFileHandler | 將日誌消息發送到磁盤文件,並支持日誌文件按時間切割 |
默認狀況下,文件會無限增加。能夠指定maxBytes
和backupCount
的特定值,以容許文件以預約的大小滾動。
噹噹前日誌文件的長度接近maxBytes
時,就會發生翻轉。若是backupCount爲>= 1
,則系統將連續建立與基本文件路徑名相同、但具備擴展名的新文件".1"
、".2"
等附於其後。
例如,若是backupCount
爲5
,而且基本文件名爲「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 |
參數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
複製代碼
Python 的logging
模塊提供了兩個支持日誌回滾的FileHandler
類,分別是RotatingFileHandler
和 TimedRotatingFileHandler
。
logging
是線程安全的,將單個進程中的多個線程日誌記錄至單個文件沒有問題。但當有多個進程向同一個日誌文件寫入日誌的時候,這兩個RotatingHandler
就會帶來問題,好比日誌丟失。
具體可參考如下博文:
Python TimedRotatingFileHandler 多進程環境下的問題和解決方法 - 喵醬的書架 - OSCHINA
logging
是線程安全的,將單個進程中的多個線程日誌記錄至單個文件也是受支持的;但將多個進程中的日誌記錄至單個文件則不受支持,由於在Python
中並無在多個進程中實現對單個文件訪問的序列化的標準方案。
若是你須要將多個進程中的日誌記錄至單個文件,有一個方案是讓全部進程都將日誌記錄至一個 SocketHandler
,而後用一個實現了套接字服務器的單獨進程一邊從套接字中讀取一邊將日誌記錄至文件。(若是願意的話,你能夠在一個現有進程中專門開一個線程來執行此項功能。) 這一部分文檔對此方式有更詳細的介紹,幷包含一個可用的套接字接收器,你本身的應用能夠在此基礎上進行適配。
這部份內容還未細看。