Python logging 模塊

Python logging 模塊

給大家剖析下Python的logging模塊。

logging模塊是Python內置的一個強大易用的日誌模塊。簡單到你只需要兩行代碼就能輸出一些東西來:

import logging
logging.warning('Hi, I print something.')

輸出:

WARNING:root:Hi, I print something.

當然,我們可不能滿足於此,你也很容易發現把上面 warning 改成 info 就輸不出來了。別急,往下看。

1. 先大體概括下logging的結構

  • 整體上來說,日誌管理最大的結構是Manager,Manager管理所有的Logger,但你不用去管這個東西,它是藏在背後默默幹活的。
  • Logger裏最根部是一個RootLogger,name是root,其他所有Logger都是在這個Logger下,通過名字,形成父子、子孫的樹杈結構。
  • Logger裏面有一些東西,Filter、Handler、LogRecord、Formatter,可以看作是一些配置,各自有各自的作用,接下來會介紹。
  • 還能用LoggerAdapter對Logger進行一定的包裝,一般我們用的很少。

logger是一個樹結構,默認有個根root,其他logger都是其上的枝椏,比如創建一個name=’A.B’的logger,其實際結構就是 root-A-B

root-A-B結構

再創建一個name=’A.C.D’的logger,結構變爲:

root-A-B-C-D結構

2. level —— 輸出哪個級別的日誌

在輸出log的時候,不能所有日誌都輸出出來,而是有所選擇,比如有時候我們只希望看到warning以上嚴重程度的,如果有太多info、debug會讓log可讀性變得很差。

我們可以通過設置logger的level來實現這一點。在logging中,將level設置如下:

LevelName LevelValue
CRITICAL/ FATAL 50
ERROR 40
WARNING/ WARN 30
INFO 20
DEBUG 10
NOTSET 0

示例:

import logging
logger = logging.getLogger('test')
logging.basicConfig()  # basicConfig是logging提供的簡單的配置方法,不用basicConfig則需要手動添加handler
logger.setLevel(logging.INFO)  # 輸出所有大於等於INFO級別的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不輸出

結果正如所料,info輸出,debug未輸出。

INFO:test:I am <info> message.

要注意的是: root 的默認級別是 WARNING!, 而且logger實際輸出時的level是取決於EffectiveLevel,即從該級往上走,遇到的第一個level不爲0的logger的level,也就是說如果你創建了logger,而沒有爲其設置level,那它默認是NOTSET,程序會往上層找,直到root,而root級別是WARNING,所以可能會導致沒有輸出日誌。

3. handler —— 輸出到哪裏

我們寫日誌一個很重要的問題就是把日誌輸出到什麼地方去,我們可能希望某些日誌在console打印出來,可能希望有更詳細的日誌輸出到log文件裏去。怎麼控制這些輸出就需要用handler了。

import logging
logger = logging.getLogger('test')
logger.addHandler(logging.StreamHandler())  # 添加StreamHandler
logger.setLevel(logging.INFO)  # 輸出所有大於INFO級別的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不輸出

我們把上面的例子稍微改動了一下,可以看到輸出如下,輸出到了console裏

I am <info> message.

這就是logging提供的最基本的一個handler,其他各種handler都是從這個handler繼承發展來的。理論上可以把日誌輸出到各種流中,stderr、文件、socket等都可以。當然logging已經將各種流handler封裝好了。

如:

import logging
logger = logging.getLogger('test')
logger.addHandler(logging.StreamHandler())
logger.addHandler(logging.FileHandler('test.log'))  # 再添一個FileHandler
logger.setLevel(logging.INFO)  # 輸出所有大於INFO級別的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不輸出

可以看到,info不僅僅輸出到了console中,還在當前文件夾下創建了一個test.log文件並輸出到了該文件中。在logging.handlers中還封裝了一堆更高級的handlers,可以瞭解下,尤其是RotatingFileHandler和TimedRotatingFileHandler,可以把你的日誌按一定規則分割成多份。你也可以自己封裝handler哦,網上有人這麼幹的。

上面我們看到了logger的級別,可以控制這個logger要輸出什麼級別的log。但這裏我們發現可以在logger裏添加handler,控制輸出log到哪裏,明顯發現,其實我們想要在不同的handler裏輸出不同級別的日誌。

比如我們想要在console裏輸出warning以上的日誌,在log文件裏輸出debug以上的日誌,該怎麼辦呢?

handler也是有級別的。

如下:

import logging
logger = logging.getLogger('test')
logger.setLevel(logging.INFO)  # 輸出所有大於INFO級別的log

# 添加StreamHandler,並設置級別爲WARNING
stream_hdl = logging.StreamHandler()
stream_hdl.setLevel(logging.WARNING)
logger.addHandler(stream_hdl)
# 添加FileHandler,並設置級別爲DEBUG
file_hdl = logging.FileHandler('test.log')
file_hdl.setLevel(logging.DEBUG)
logger.addHandler(file_hdl)

logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不輸出

設置了WARNING級別的StreamHandler以及DEBUG級別的FileHandler。在logger.info()執行時,程序判斷log級別(info) >= logger級別(INFO),可以輸出。

然後程序判斷log級別(info) < StreamHandler級別(WARNING),不在stderr輸出;同時log級別(info) > FileHandler級別(DEBUG),在test.log中輸出。

而logger.debug()的級別 < logger級別(INFO),所以不會在stderr和test.log輸出。

其實上面的程序有個問題,就是設置了logger是INFO,那麼FileHandler是DEBUG沒有什麼意義的,DEBUG級別的日誌過不了logger那關。

你在寫代碼的時候就需要注意了,要給logger添加handler,並且注意logger和handler的level,否則可能會輸不出你想要的log哦。

4. formatter —— 輸出日誌的格式

細心的話可以發現,我們後來自己添加的handler輸出的log是沒有格式的,就僅僅是輸出而已。但basicConfig()輸出的log是有格式的(雖然很醜)。

不同在於basicConfig()中的handler是帶有formatter的。我們要添加formatter就需要用到logging中的另一個類Formatter

import logging
logger = logging.getLogger('test')
logger.setLevel(logging.INFO)  # 輸出所有大於INFO級別的log
fmt = logging.Formatter('%(name)s - %(levelname)s - %(asctime)s - %(message)s')
# 添加StreamHandler,並設置級別爲WARNING
stream_hdl = logging.StreamHandler()
stream_hdl.setLevel(logging.WARNING)
stream_hdl.setFormatter(fmt)
logger.addHandler(stream_hdl)
# 添加FileHandler,並設置級別爲DEBUG
file_hdl = logging.FileHandler('test.log')
file_hdl.setLevel(logging.DEBUG)
file_hdl.setFormatter(fmt)
logger.addHandler(file_hdl)

logger.info('I am <info> message.')
logger.debug('I am <debug> message.')  # 不輸出

如上例中,我們添加了formatter,格式爲 (logger name) - (level name) - (當前時間) - (日誌信息),輸出的結果爲:

test - INFO - 2017-09-06 16:45:36,977 - I am <info> message.

是不是漂亮多了,logging的formatter可以輸出的不止這幾個信息,還有很多:

  • %(name)s ——Name of the logger (logging channel)
  • %(levelno)s ——Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL)
  • %(levelname)s ——Text logging level for the message (“DEBUG”, “INFO”, “WARNING”, “ERROR”, “CRITICAL”)
  • %(pathname)s ——Full pathname of the source file where the logging call was issued (if available)
  • %(filename)s ——Filename portion of pathname
  • %(module)s ——Module (name portion of filename)
  • %(lineno)d ——Source line number where the logging call was issued(if available)
  • %(funcName)s ——Function name
  • %(created)f ——Time when the LogRecord was created (time.time() return value)
  • %(asctime)s ——Textual time when the LogRecord was created
  • %(msecs)d ——Millisecond portion of the creation time
  • %(relativeCreated)d ——Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded (typically at application startup time)
  • %(thread)d ——Thread ID (if available)
  • %(threadName)s ——Thread name (if available)
  • %(process)d ——Process ID (if available)
  • %(message)s ——The result of record.getMessage(), computed just as the record is emitted

我們也可以給Formatter傳入第二個參數,修改日期格式,比如 %Y-%m-%d ,則輸出的log就沒有時分秒了。

這樣就可以定製log的格式了,還可以爲不同的handler制定不同的formatter,以適應不同情況下的閱讀或傳輸需求。

5. Filter和LoggerAdapter

最後這裏有兩個我們不常用到的東西,懶得多寫了。

6. 重複寫日誌的問題

這裏有一個坑需要特別說明下,就是add handler的問題。一個logger創建之後不要重複添加handler,有時候我們多個文件都調用我們封裝好的logger的類,就有可能會發生重複添加handler的情況,具體的看博主這篇文章——python logging 重複寫日誌問題

7. 差點忘了

差點忘了配置的問題,我們可以通過多種途徑配置logger,像上面用過的basicConfig()(沒有用參數,可以自己閱讀源碼,很簡單的幾個參數),或者像上面的例子手動添加handler、設置formatter,logging還給了我們其他高級的配置方法:

logging.config裏有多種配置方法,像fileConfig,可以直接讀一個文件;像dictConfig,可以傳一個dict進行配置(django就是這麼配置的);似乎還能起個socket,來實時修改config,聽起來很吊的樣子。

不過我們用其中的一種就可以了,隨便配嘍。

比如,像博主這樣 怎樣從0開始搭建一個測試框架_2

就說這些,跟其他人的講法好像不太一樣,結合着來看吧。

有什麼好的建議或者問題,可以留言或者加我的QQ羣:455478219討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章