先介紹一下咱們爲何要使用日誌,日常咱們編寫程序爲了驗證程序運行與debug,一般會使用print函數來對一些中間結果進行輸出驗證,在驗證成功後再將print語句註釋或刪除掉。這樣作在小型程序中還比較靈活,可是對於大型項目來講,就十分繁瑣了----->因此使用日誌log就很天然了,日誌能夠調整日誌級別,來決定咱們是否輸出對應級別的日誌,同時還能夠將日誌導入文件記錄下來。函數
再介紹一下logging中的log級別:ui
Level | Numeric Value |
---|---|
logging.CRITICAL | 50 |
logging.ERROR | 40 |
logging.WARNING | 30 |
logging.INFO | 20 |
logging.DEBUG | 10 |
實際上這些level都是整數值,可由如type(logging.info)驗證爲int類型。debug
logging模塊的模塊級使用方法就是使用一些模塊級接口函數。並且還有一個比較重要的是對日誌的輸出形式和輸出目的地等進行設置。日誌
接口函數也是對應日誌級別而輸出信息的:
logging.debug(msg)
logging.info(msg)
logging.warning(msg)
logging.error(msg)
logging.critical(msg)
這幾個函數除了日誌級別上的區別,其實都是使用默認的root logger來對信息進行log的,它是處於日誌器層級關係最頂層的日誌器,且該實例是以單例模式存在的。見源碼:code
def info(msg, *args, **kwargs): if len(root.handlers) == 0: basicConfig() root.info(msg, *args, **kwargs)
這裏能夠見到在logging模塊的info函數中:(1)首先進行了一個對於root logger的handlers屬性的長度判斷是否調用basicConfig函數。(2)以後是調用root logger的info函數來實現功能的。
這裏對於第(2)點咱們進一下探尋:orm
root = RootLogger(WARNING)
在logging的源碼中能夠看到如上語句,即咱們將logging模塊import後,其實已經默認的建立了一個root logger對象,而且以後咱們本身建立的logger都是root logger的子類。對象
對於日誌的設置,咱們是使用logging.basicConfig(**kwargs)函數,它是對root logger進行設置的,通常使用較多的關鍵字參數以下:接口
對於logging.basicConfig函數有一點須要注意:咱們不能在該函數前使用任何模塊級日誌輸出函數如logging.info、logging.error,由於它們會調用一個不帶參的basicConfig函數,使得logging.basicConfig函數失效。見源碼(因爲代碼過多,建議參考註釋進行閱讀):ci
def basicConfig(**kwargs): _acquireLock() try: #這裏因爲不帶參調用basicConifg, #而root.handlers默認爲空列表 #在Logger定義中可見self.handlers被設爲[], #而默認的root實例在建立時只指定了log級別 #因此if條件必然經過 if len(root.handlers) == 0: #因爲不帶參,因此handlers必爲None handlers = kwargs.pop("handlers", None) if handlers is None: #這裏因爲不帶參,因此便是handlers爲None #經過上面的if判斷,但kwargs一樣爲None, #因此該if不經過 if "stream" in kwargs and "filename" in kwargs: raise ValueError("'stream' and 'filename' should not be " "specified together") else: if "stream" in kwargs or "filename" in kwargs: raise ValueError("'stream' or 'filename' should not be " "specified together with 'handlers'") #這裏因爲handlers爲None經過if判斷繼續執行 if handlers is None: filename = kwargs.pop("filename", None) mode = kwargs.pop("filemode", 'a') if filename: h = FileHandler(filename, mode) #不帶參,kwargs爲None,因此filename #在上面的執行語句的返回值爲None,因此 #執行這個else分支 else: stream = kwargs.pop("stream", None) h = StreamHandler(stream) #注意這裏,十分重要,可見handlers終於不爲None #被賦予了一個列表,該列表有一個元素h handlers = [h] dfs = kwargs.pop("datefmt", None) style = kwargs.pop("style", '%') if style not in _STYLES: raise ValueError('Style must be one of: %s' % ','.join( _STYLES.keys())) fs = kwargs.pop("format", _STYLES[style][1]) fmt = Formatter(fs, dfs, style) #再看這裏,十分重要 for h in handlers: #這個無所謂,就是對format進行默認設置 if h.formatter is None: h.setFormatter(fmt) #這裏最爲關鍵,可見root.addHandler(h)函數 #會把h添加進root.handlers列表中,那麼很顯然 #root.handlers再也不是一個空列表 root.addHandler(h) level = kwargs.pop("level", None) if level is not None: root.setLevel(level) if kwargs: keys = ', '.join(kwargs.keys()) raise ValueError('Unrecognised argument(s): %s' % keys) finally: _releaseLock()
因此便是不帶參調用basicConfig(),可是通過其函數體執行,root.handlers的列表長度會不爲0,因此以後再調用logging.basicConifg函數時,對root.handlers判斷,就會所以而直接略過函數體中try部分(主要部分),直接執行finally,沒有進行任何設置。字符串
在logging模塊中logger對象歷來都不是直接實例化,而是經過一個模塊級藉口完成:logging.getLogger(name=None),注意咱們建立的logger都是root logger的子類。而經過咱們本身建立的logger對象,使用日誌記錄也是和模塊級接口同樣的:
logger.debug(msg)
logger.info(msg)
logger.warning(msg)
logger.error(msg)
logger.critical(msg)
一樣對於日誌格式的設置也是經過logging.basicConfig函數完成的,雖然該函數是對root logger的日誌格式設置,但因爲咱們定義的logger類都是root logger的子類,因此便可以沿用該設置。而且對於對象級接口,如logger.info函數:
def info(self, msg, *args, **kwargs): if self.isEnabledFor(INFO): self._log(INFO, msg, args, **kwargs)
可見其中沒有對basicConfig函數的調用,因此也就沒有修改root.handlers列表,即不會發生上文的logging.basciConfig函數失效的問題。