Python:使用logging模塊記錄日誌

logging模塊簡介

logging模塊是Python內置的標準模塊,主要用於輸出運行日誌,可以設置輸出日誌的等級、日誌保存路徑、日誌文件回滾等;

相比print(),具備如下優點:

     1).可以通過設置不同的日誌等級,在release版本中只輸出重要信息,而不必顯示大量的調試信息;

     2).print()將所有信息都輸出到標準輸出中,嚴重影響開發者從標準輸出中查看其它數據;logging則可以由開發者決定將信息輸出到什麼地方,以及怎麼輸出;

簡單使用

import logging

logging.critical('等級我是天下第一,不服來辯!')
logging.error('呦呦呦,我說自己是第二,沒人敢說自己是第一!')
logging.warning('前面兩個別吵了,讓我這第三情何以堪!')
logging.info('各位少俠,別來無恙,區區第四,不值一提!')
logging.debug('我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!')

輸出:

CRITICAL:root:等級我是天下第一,不服來辯!
ERROR:root:呦呦呦,我說自己是第二,沒人敢說自己是第一!
WARNING:root:前面兩個別吵了,讓我這第三情何以堪!

從上面輸出可以看出值打印出來前三條記錄,這是爲什麼呢?因爲默認生成的root logger的level等級是logging.WARNING,低於該級別的就不輸出了。

當我們設置了輸出 level,系統便只會輸出 level 數值大於或等於該 level 的的日誌結果,例如我們設置了輸出日誌 level 爲 INFO,那麼輸出級別大於等於 INFO 的日誌。

關於各個等級高低介紹見表格:

日誌等級
等級 介紹 數值
CRITICAL 打印critical級別,一個嚴重的錯誤,這表明程序本身可能無法繼續運行 50
FATAL 打印的也是critical級別,致命錯誤,使用很少 50
ERROR 打印error,critical級別的日誌,更嚴重的問題,軟件沒能執行一些功能 40
WARNING 打印warning,error,critical級別的日誌,一個跡象表明,一些意想不到的事情發生了,或表明一些問題在不久的將來(例如:磁盤空間低”),這個軟件還能按預期工作 30
WARN 是WARNING的簡寫模式,功能和等級與WARNING一樣,在Python3中已被廢棄 30
INFO 打印info,warning,error,critical級別的日誌,確認一切按預期運行 20
DEBUG 打印全部的日誌,詳細的信息,通常只出現在診斷問題上 10
NOTSET 如果需要顯示低於WARNING級別的內容,可以引入NOTSET級別來顯示: 0

下面我們把之前的代碼加工一下,增加一些配置,使輸出的日誌信息更易讀。

# -*- coding:utf-8 -*-
import logging

logging.basicConfig(level=logging.DEBUG,format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

logger.critical('等級我是天下第一,不服來辯!')
logger.error('呦呦呦,我說自己是第二,沒人敢說自己是第一!')
logger.warning('前面兩個別吵了,讓我這第三情何以堪!')
logger.info('各位少俠,別來無恙,區區第四,不值一提!')
logger.debug('我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!')

代碼分析:

在這裏我們首先引入了 logging 模塊,然後進行了一下基本的配置,這裏通過 basicConfig 配置了 level 信息和 format 信息,這裏 level 配置爲 DEBUG ,另外這裏指定了 format 格式的字符串,包括 asctime、name、levelname、message 四個內容,分別代表運行時間、模塊名稱、日誌級別、日誌內容,這樣輸出內容便是這四者組合而成的內容了,這就是 logging 的全局配置。

接下來聲明瞭一個 Logger 對象,它就是日誌輸出的主類,調用對象的 info() 方法就可以輸出 INFO 級別的日誌信息,調用 debug() 方法就可以輸出 DEBUG 級別的日誌信息,非常方便。在初始化的時候我們傳入了模塊的名稱,這裏直接使用 __name__ 來代替了,就是模塊的名稱,如果直接運行這個腳本的話就是 __main__,如果是 import 的模塊的話就是被引入模塊的名稱,這個變量在不同的模塊中的名字是不同的,所以一般使用 __name__ 來表示就好了.

看下輸出結果:

2018-11-14 11:27:33,635 - __main__ - CRITICAL - 等級我是天下第一,不服來辯!
2018-11-14 11:27:33,635 - __main__ - ERROR - 呦呦呦,我說自己是第二,沒人敢說自己是第一!
2018-11-14 11:27:33,635 - __main__ - WARNING - 前面兩個別吵了,讓我這第三情何以堪!
2018-11-14 11:27:33,635 - __main__ - INFO - 各位少俠,別來無恙,區區第四,不值一提!
2018-11-14 11:27:33,635 - __main__ - DEBUG - 我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!

上面這寫只是 logging 模塊的一小部分功能,接下來我們首先來全面瞭解一下 basicConfig 的參數都有哪些:

  • filename:即日誌輸出的文件名,如果指定了這個信息之後,實際上會啓用 FileHandler,而不再是 StreamHandler,這樣日誌信息便會輸出到文件中了。
  • filemode:這個是指定日誌文件的寫入方式,有兩種形式,一種是 w,一種是 a,分別代表清除後寫入和追加寫入。
  • format:指定日誌信息的輸出格式,即上文示例所示的參數,詳細參數可以參考:docs.python.org/3/library/l…,部分參數如下所示:
    • %(levelno)s:打印日誌級別的數值。
    • %(levelname)s:打印日誌級別的名稱。
    • %(pathname)s:打印當前執行程序的路徑,其實就是sys.argv[0]。
    • %(filename)s:打印當前執行程序名。
    • %(funcName)s:打印日誌的當前函數。
    • %(lineno)d:打印日誌的當前行號。
    • %(asctime)s:打印日誌的時間。
    • %(thread)d:打印線程ID。
    • %(threadName)s:打印線程名稱。
    • %(process)d:打印進程ID。
    • %(processName)s:打印線程名稱。
    • %(module)s:打印模塊名稱。
    • %(message)s:打印日誌信息。
  • datefmt:指定時間的輸出格式。
  • style:如果 format 參數指定了,這個參數就可以指定格式化時的佔位符風格,如 %、{、$ 等。
  • level:指定日誌輸出的類別,程序會輸出大於等於此級別的信息。
  • stream:在沒有指定 filename 的時候會默認使用 StreamHandler,這時 stream 可以指定初始化的文件流。
  • handlers:可以指定日誌處理時所使用的 Handlers,必須是可迭代的。 

以上這些就是basicConfig 的一些全局的配置,感興趣的小夥伴可以在代碼中嘗試下。

另外我們同樣可以使用 Formatter、Handler 進行更靈活的處理,下面我們來了解一下。

Handler

下面我們先來了解一下 Handler 的用法,看下面的實例:

# -*- coding:utf-8 -*-
import logging

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)
handler = logging.FileHandler('log.log')
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.critical('等級我是天下第一,不服來辯!')
logger.error('呦呦呦,我說自己是第二,沒人敢說自己是第一!')
logger.warning('前面兩個別吵了,讓我這第三情何以堪!')
logger.info('各位少俠,別來無恙,區區第四,不值一提!')
logger.debug('我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!')

這裏沒有再使用 basicConfig 全局配置,而是先聲明瞭一個 Logger 對象,然後指定了其對應的 Handler 爲 FileHandler 對象,然後 Handler 對象還單獨指定了 Formatter 對象單獨配置輸出格式,最後給 Logger 對象添加對應的 Handler 即可,最後可以發現日誌就會被輸出到 log.log 中,內容如下:

2018-11-14 11:52:43,444 - __main__ - CRITICAL - 等級我是天下第一,不服來辯!
2018-11-14 11:52:43,445 - __main__ - ERROR - 呦呦呦,我說自己是第二,沒人敢說自己是第一!
2018-11-14 11:52:43,445 - __main__ - WARNING - 前面兩個別吵了,讓我這第三情何以堪!
2018-11-14 11:52:43,445 - __main__ - INFO - 各位少俠,別來無恙,區區第四,不值一提!
2018-11-14 11:52:43,445 - __main__ - DEBUG - 我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!

另外我們還可以使用其他的 Handler 進行日誌的輸出,logging 模塊提供的 Handler 有:

  • StreamHandler:logging.StreamHandler;日誌輸出到流,可以是 sys.stderr,sys.stdout 或者文件。
  • FileHandler:logging.FileHandler;日誌輸出到文件。
  • BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日誌回滾方式。
  • RotatingHandler:logging.handlers.RotatingHandler;日誌回滾方式,支持日誌文件最大數量和日誌文件回滾。
  • TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日誌回滾方式,在一定時間區域內回滾日誌文件。
  • SocketHandler:logging.handlers.SocketHandler;遠程輸出日誌到TCP/IP sockets。
  • DatagramHandler:logging.handlers.DatagramHandler;遠程輸出日誌到UDP sockets。
  • SMTPHandler:logging.handlers.SMTPHandler;遠程輸出日誌到郵件地址。
  • SysLogHandler:logging.handlers.SysLogHandler;日誌輸出到syslog。
  • NTEventLogHandler:logging.handlers.NTEventLogHandler;遠程輸出日誌到Windows NT/2000/XP的事件日誌。
  • MemoryHandler:logging.handlers.MemoryHandler;日誌輸出到內存中的指定buffer。
  • HTTPHandler:logging.handlers.HTTPHandler;通過”GET”或者”POST”遠程輸出到HTTP服務器。

下面我們使用三個 Handler 來實現日誌同時輸出到控制檯、文件、HTTP 服務器:

# -*- coding:utf-8 -*-
import logging
from logging.handlers import HTTPHandler
import sys

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

# FileHandler 將日誌輸出到文件log.log
file_handler = logging.FileHandler('log.log')
file_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# StreamHandler 將日誌輸出到控制檯
stream_hanlder = logging.StreamHandler(sys.stdout)
stream_hanlder.setLevel(level=logging.DEBUG)
logger.addHandler(stream_hanlder)

# HTTPHandler 將日誌輸出到HTTP服務器
http_handler = HTTPHandler(host='localhost:8080', url='log', method='POST')
logger.addHandler(http_handler)

logger.critical('等級我是天下第一,不服來辯!')
logger.error('呦呦呦,我說自己是第二,沒人敢說自己是第一!')
logger.warning('前面兩個別吵了,讓我這第三情何以堪!')
logger.info('各位少俠,別來無恙,區區第四,不值一提!')
logger.debug('我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!')

運行之前我們需要先啓動 HTTP Server,並運行在 8080 端口,其中 log 接口是用來接收日誌的接口。

我是在Windows環境搭建的http-server服務器,具體搭建教程看鏈接:http://www.fefuns.com/2018/07/03/224/

運行之後控制檯輸出會輸出如下內容:

等級我是天下第一,不服來辯!
呦呦呦,我說自己是第二,沒人敢說自己是第一!
前面兩個別吵了,讓我這第三情何以堪!
各位少俠,別來無恙,區區第四,不值一提!
我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!

log.log文件會寫入下面的內容:

2018-11-14 14:51:09,991 - __main__ - CRITICAL - 等級我是天下第一,不服來辯!
2018-11-14 14:51:11,038 - __main__ - ERROR - 呦呦呦,我說自己是第二,沒人敢說自己是第一!
2018-11-14 14:51:12,048 - __main__ - WARNING - 前面兩個別吵了,讓我這第三情何以堪!
2018-11-14 14:51:13,057 - __main__ - INFO - 各位少俠,別來無恙,區區第四,不值一提!

HTTP Server 會收到控制檯輸出的信息。

但是這兒本人有個疑問,就是HTTP Server接收到的日誌信息存放在哪兒了?還望懂的大神告知!

這樣一來,我們就通過設置多個 Handler 來控制了日誌的多目標輸出。

另外值得注意的是,在這裏 StreamHandler 對象我們沒有設置 Formatter,因此控制檯只輸出了日誌的內容,而沒有包含時間、模塊等信息,而 FileHandler 我們通過 setFormatter() 方法設置了一個 Formatter 對象,因此輸出的內容便是格式化後的日誌信息。

另外每個 Handler 還可以設置 level 信息,最終輸出結果的 level 信息會取 Logger 對象的 level 和 Handler 對象的 level 的交集。

Format

在進行日誌格式化輸出的時候,我們可以不藉助於 basicConfig 來全局配置格式化輸出內容,可以藉助於 Formatter 來完成,下面我們再來單獨看下 Formatter 的用法:

# -*- coding:utf-8 -*-
import logging

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.WARNING)

stream_hanlder = logging.StreamHandler()
formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
stream_hanlder.setFormatter(formatter)
logger.addHandler(stream_hanlder)

logger.critical('等級我是天下第一,不服來辯!')
logger.error('呦呦呦,我說自己是第二,沒人敢說自己是第一!')
logger.warning('前面兩個別吵了,讓我這第三情何以堪!')
logger.info('各位少俠,別來無恙,區區第四,不值一提!')
logger.debug('我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!')

在這裏我們指定了一個 Formatter,並傳入了 fmt 參數,這樣就指定了日誌結果的輸出格式,當然我們還可以設置其他的參數,比如時間格式參數datefmt等,然後 handler 通過 setFormatter() 方法設置此 Formatter 對象即可,輸出結果如下:

2018-11-14 15:19:23,733 - __main__ - CRITICAL - 等級我是天下第一,不服來辯!
2018-11-14 15:19:23,734 - __main__ - ERROR - 呦呦呦,我說自己是第二,沒人敢說自己是第一!
2018-11-14 15:19:23,734 - __main__ - WARNING - 前面兩個別吵了,讓我這第三情何以堪!

由上可見,我們可以爲每個 Handler 單獨配置輸出的格式,非常靈活。

捕獲 Traceback

如果遇到錯誤,我們更希望報錯時出現的詳細 Traceback 信息,便於調試,利用 logging 模塊我們可以非常方便地實現這個記錄,我們用一個實例來感受一下:

# -*- coding:utf-8 -*-
import logging

formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)

# FileHandler 將日誌輸出到日誌文件log.log
file_handler = logging.FileHandler('log.log')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# StreamHandler 將日誌輸出到控制檯
stream_hanlder = logging.StreamHandler()
stream_hanlder.setFormatter(formatter)
logger.addHandler(stream_hanlder)

logger.critical('等級我是天下第一,不服來辯!')
try:
    result = 10 / 0    # 此處執行一條非法的語法
except Exception:
    logger.error('呦呦呦,我說自己是第二,沒人敢說自己是第一!', exc_info=True)

logger.warning('前面兩個別吵了,讓我這第三情何以堪!')
logger.info('各位少俠,別來無恙,區區第四,不值一提!')
logger.debug('我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!')

這裏我們在 error() 方法中添加了一個參數,將 exc_info 設置爲了 True,這樣我們就可以輸出執行過程中的信息了,即完整的 Traceback 信息。

輸出到日誌文件和控制檯的運行結果如下:

2018-11-14 15:49:28,458 - __main__ - CRITICAL - 等級我是天下第一,不服來辯!
2018-11-14 15:49:28,458 - __main__ - ERROR - 呦呦呦,我說自己是第二,沒人敢說自己是第一!
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/test2.py", line 31, in <module>
    result = 10 / 0
ZeroDivisionError: division by zero
2018-11-14 15:49:28,458 - __main__ - WARNING - 前面兩個別吵了,讓我這第三情何以堪!
2018-11-14 15:49:28,458 - __main__ - INFO - 各位少俠,別來無恙,區區第四,不值一提!
2018-11-14 15:49:28,458 - __main__ - DEBUG - 我排名第五,不接受反駁!我不會說自己是倒數第一的,哼!

從輸出信息可以看到,error()將代碼執行過程的報錯的信息記錄下來了,這樣我們就能非常方便地排查。

 

logging模塊本文就介紹到這,很多內容轉載至:https://cuiqingcai.com/6080.html

請查看原文,繼續學習!

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