Python 內置logging 使用詳細介紹

logging 的主要作用

提供日志記錄的接口和眾多處理模塊,供用戶存儲各種格式的日志,幫助調試程序或者記錄程序運行過程中的輸出信息。

logging 日志等級

logging 日志等級分為五個等級,優先級從高到低依次是 :

**CRITICAL; ** 程序嚴重錯誤

**ERROR; **程序錯誤/部分功能錯誤

**WARNING; **程序有發生錯誤的可能

**INFO; **程序正常運行時的信息

DEBUG程序調試信息

默認的日志的記錄等級為 WARNING, 即當日志的等級大於獲等於 WARNING 時才會被記錄。

一般常用的記錄等級為 INFO,其用於記錄程序的正常運行的一些信息(類似於print)。

當日志的等級達到 WARNING 以上時,表明此時程序不能正常運行;

logging 的基礎函數

logging.basicConfig(**kwargs)

在沒有顯式的進行創建記錄器(logger)時,會默認創建一個root logger,而logging.basicConfig(**kwargs) 可以創建帶有默認的Formatter的streamHandle並將其添加到根日志記錄器中來初始化基本配置。

比如

import logging
logging.debug('Debug code!')
logging.info('Run code!')
logging.warning('Watch out!')  
logging.error('This is an error')
logging.critical('This is a ciritical')

上面代碼中 logging 並沒有顯式的創建logger( logging.getLogger ), 其在直接使用debug(), info(), warning(), error(), critical() 時會使用默認的 root logger,並會自動調用 自定義的或者默認的logging.basicConfig(**kwargs) 初始化 root logger。

自定義的 logging.basicConfig(**kwargs) 中的參數 有以下的主要的選項:

參數 功能
filename 指定保存日志的文件名,用指定文件名創建一個FileHandler,記錄的日志會保存到該文件中
format 指定輸出的格式和內容,默認是以冒號分割的levalname、name 和 message
datefmt 使用指定的日期/時間格式,與 time.strftime() 所接受的格式相同。
level 指定根日志記錄器級別,默認為 logging.WARNING
stream 指定日志的輸出流,可以指定輸出到sys.stderr,std.stdout 或 文件,默認輸出到sys.stderr。使用指定的流初始化StramHandler,註意:stream和filename參數不兼容,如果兩者同時使用,則會引發ValueError 錯誤

例如下面通過自定義 logging.basicConfig(**kwargs) 來初始化 root logger 來獲得DEBUG級別及以上的日志記錄並保存到 log.txt 文件中。

import logging
logging.basicConfig(filename='./log.txt',
                        format='%(asctime)s-%(name)s-%(levelname)s-%(message)s-%(funcName)s:%(lineno)d',
                        level=logging.DEBUG)
logging.debug('Debug code!')
logging.info('Run code!')
logging.warning('Watch out!')  
logging.error('This is an error')
logging.critical('This is a ciritical')

logging 的四大組件(類)

Logger

除瞭根記錄器(root logger)外,最主要的是可以自己創建日志記錄器。

通過模塊級別的函數 logging.getLogger(name) 實例化記錄器

默認情況下,記錄器采用層級結構,通過 . 來區分不同的層級。比如 有個名叫 foo 的記錄器 則 foo.afoo.b 都是 foo 的子級記錄器。當然,最開始的或者說最上層的記錄器就是 root logger。如果 name=None,構建的是root logger。

可以直接用當前模塊的名稱當作記錄器的名字 logging.getLogger(__name__)

子級記錄器通常不需要單獨設置日志級別以及 Handler,如果子級記錄器沒有單獨設置,則它的行為會委托給父級。比如說,記錄器foo的級別為INFO,而foo.afoo.b 都不設置日志級別。此時foo.afoo.b 會遵循foo 的級別設置,即隻記錄大於等於INFO級別的日志;而如果foo也沒設置的話,就會找到根記錄器root logger,root默認的級別為WARGING。

logger類的一些常用的方法

方法 功能描述
Logger.setLevel() 設置日志器(Logger)將會處理的日志消息級別
Logger.addHandler() 添加一個handler對象
Logger.removeHandler() 移除一個handler對象
Logger.addFilter() 添加一個filter對象
Logger.removeFilter() 移除一個filter對象
Logger.debug() 設置DEBUG級別的日志記錄
Logger.info() 設置INFO級別的日志記錄
Logger.warning() 設置WARNING級別的日志記錄
Logger.error() 設置ERROR級別的日志記錄
Logger.critical() 設置CRITICAL級別的日志記錄
Logger.exception() 輸出堆棧追蹤信息
Logger.log() 設置一個自定義的level參數來創建一個日志記錄

logger 結合 後面要介紹的其他的三個組件可以實現以下的功能:

  • Logger需要通過handler將日志信息輸出到目標位置,目標位置可以是sys.stdout和文件等(這與logging.basicConfig(**kwargs) 設置中不太一致)。
  • 一個Logger可以設置不同的Handler,而不同的Handler可以將日志輸出到不同的位置(不同的日志文件),並且每個Handler都可以設置自己的filter從而實現日志過濾,保留實際項目中需要的日志。同時每個Handler也可以設置不同的Formatter,在每個Formatter實現同一條日志以不同的格式輸出到不同的地方。

Handle

處理器;其可以控制記錄的日志輸出到什麼地方(標準輸出/文件/…),同時處理器也可以添加 過濾器(filter)和格式控制器(formatter)來控制輸出的內容和輸出的格式。

其具有幾種常見的處理器:

  • logging.StreamHandler 標準流處理器,將消息發送到標準輸出流、錯誤流 –> logging.StreamHandler(sys.stdout) # sys.stdout 表示的是指向控制臺即標準輸出;當我們在 Python 中打印對象調用 print obj 時候,事實上是調用瞭 sys.stdout.write(obj+'\n')。
  • print 將你需要的內容打印到瞭控制臺,然後追加瞭一個換行符
  • logging.FileHandler 文件處理器,將消息發送到文件 –> logging.FileHandler(log_path)
  • logging.RotatingFileHandler 文件處理器,文件達到指定大小後,啟用新文件存儲日志
  • logging.TimedRotatingFileHandler 文件處理器,日志以特定的時間間隔輪換日志文件

handle 類的一些常用的方法

Handler.setLevel() 設置處理器將會處理的日志消息的最低嚴重級別
Handler.setFormatter() 為處理器設置一個格式對象
Handler.addFilter() 為處理器添加一個過濾器對象
Handler.removeFilter() 為處理器刪除一個過濾器對象
logging.StramHandler() 將日志消息發送到輸出Stream,如std.out,std.err
logging.FilterHandler() 將日志消息發送到磁盤文件,默認情況文件大小會無線增長
RotationFileHandler() 將日志消息發送到磁盤文件,支持日志文件按大小切割
TimeRotatingFileHandler() 將日志消息發送到磁盤文件,並支持日志文件按時間切割
logging.handers.HTTPHandler() 將日志消息通過GET或POST的方式發送給一個HTTP服務器
logging.handlers.SMTPHandler() 將日志消息發送email地址

Filter

filter組件用來過濾 logger 對象,一個 filter 可以直接添加到 logger對象上,也可以添加到 handler 對象上,而如果在logger和handler中都設置瞭filter,則日志是先通過logger的filter,再通過handler的filter。由於所有的信息都可以經過filter,所以filter不僅可以過濾信息,還可以增加信息。

Filter 類的實例化對象可以通過 logging.Filter(name) 來創建,其中name 為 記錄器的名字,如果沒有創建過該名字的記錄器,就不會輸出任何日志:

filter = logging.Filter("foo.a")

基本過濾器類隻允許低於指定的日志記錄器層級結構中低於特定層級的事件,例如 這個用 foo.a 初始化的過濾器,則foo.a.b;foo.a.c 等日志記錄器記錄的日志都可以通過過濾器,而foo.c; a.foo 等就不能通過。如果name為空字符串,則所有的日志都能通過。

Filter 類 有 三個方法 :

  • addFilter(filter) : 為 logger(logger..addFilter(filter)) 或者 handler(handler..addFilter(filter)) 增加過濾器
  • removeFilter(filter) : 為 logger 或者 handler 刪除一個過濾器
  • filter(record) : 表示是否要記錄指定的記錄?返回零表示否,非零表示是。一般自定義Filter需要繼承Filter基類,並重寫filter方法

Formatter

格式化日志的輸出;實例化:formatter = logging.Formatter(fmt=None,datefmt=None); 如果不指明 fmt,將默認使用 ‘%(message)s’ ,如果不指明 datefmt,將默認使用 ISO8601 日期格式。

其中 fmt 參數 有以下選項:

%(name)s Logger的名字
%(levelno)s 數字形式的日志級別
%(levelname)s 文本形式的日志級別;如果是logger.debug則它是DEBUG,如果是logger.error則它是ERROR
%(pathname)s 調用日志輸出函數的模塊的完整路徑名,可能沒有
%(filename)s 調用日志輸出函數的模塊的文件名
%(module)s 調用日志輸出函數的模塊名
%(funcName)s 調用日志輸出函數的函數名
%(lineno)d 調用日志輸出函數的語句所在的代碼行
%(created)f 當前時間,用UNIX標準的表示時間的浮 點數表示
%(relativeCreated)d 輸出日志信息時的,自Logger創建以 來的毫秒數
%(asctime)s 字符串形式的當前時間。默認格式是 “2003-07-08 16:49:45,896”。逗號後面的是毫秒
%(thread)d 線程ID。可能沒有
%(threadName)s 線程名。可能沒有
%(process)d 進程ID。可能沒有
%(message)s 用戶輸出的消息; 假如有logger.warning("NO Good"),則在%(message)s位置上是字符串NO Good

例如:

formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s')		# -表示右對齊 8表示取8位
handler.formatter = formatter

datefmt 參數 有以下選項:

參數 含義
%y 兩位數的年份表示(00-99)
%Y 四位數的年份表示(000-9999)
%m 月份(01-12)
%d 月內中的一天(0-31)
%H 24小時制小時數(0-23)
%I 12小時制小時數(01-12)
%M 分鐘數(00=59)
%S 秒 (00-59)

例如:

formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s')		# -表示右對齊 8表示取8位
handler.formatter = formatter

datefmt 參數 有以下選項:

參數 含義
%y 兩位數的年份表示(00-99)
%Y 四位數的年份表示(000-9999)
%m 月份(01-12)
%d 月內中的一天(0-31)
%H 24小時制小時數(0-23)
%I 12小時制小時數(01-12)
%M 分鐘數(00=59)
%S 秒 (00-59)

例子:

formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s","%Y%m%d-%H:%M:%S")
handler.formatter = formatter

logging 的配置

  • conf 形式的配置

在 loguser.conf 中 寫入相關的信息

[loggers]
keys=root,fileLogger,rotatingFileLogger

[handlers]
keys=consoleHandler,fileHandler,rotatingFileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=INFO
handlers=consoleHandler

[logger_fileLogger]
level=INFO
handlers=fileHandler
qualname=fileLogger
propagate=0

[logger_rotatingFileLogger]
level=INFO
handlers=consoleHandler,rotatingFileHandler
qualname=rotatingFileLogger
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=simpleFormatter
args=("logs/fileHandler_test.log", "a")

[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=WARNING
formatter=simpleFormatter
args=("logs/rotatingFileHandler.log", "a", 10*1024*1024, 50)

[formatter_simpleFormatter]
format=%(asctime)s - %(module)s - %(levelname)s -%(thread)d : %(message)s
datefmt=%Y-%m-%d %H:%M:%S
  • 在使用logger時,直接導入配置文件即可
from logging import config

with open('./loguser.conf', 'r', encoding='utf-8') as f:
	## 加載配置
    config.fileConfig(f)
    ## 創建同名Logger,其按照配置文件的handle,formatter,filter方法初始化
    logger = logging.getLogger(name="fileLogger")
  • yaml 形式配置文件

在 loguser.yaml文件 中 配置相關信息

version: 1
disable_existing_loggers: False
# formatters配置瞭日志輸出時的樣式
# formatters定義瞭一組formatID,有不同的格式;
formatters:
  brief:
      format: "%(asctime)s - %(message)s"
  simple:
      format: "%(asctime)s - [%(name)s] - [%(levelname)s] :%(levelno)s: %(message)s"
      datefmt: '%F %T'
# handlers配置瞭需要處理的日志信息,logging模塊的handler隻有streamhandler和filehandler
handlers:
  console:
      class : logging.StreamHandler
      formatter: brief
      level   : DEBUG
      stream  : ext://sys.stdout
  info_file_handler:
      class : logging.FileHandler
      formatter: simple
      level: ERROR
      filename: ./logs/debug_test.log
  error_file_handler:
    class: logging.handlers.RotatingFileHandler
    level: ERROR
    formatter: simple
    filename: ./logs/errors.log
    maxBytes: 10485760 # 10MB #1024*1024*10
    backupCount: 50
    encoding: utf8

loggers:
#fileLogger, 就是在代碼中通過logger = logging.getLogger("fileLogger")來獲得該類型的logger
  my_testyaml:
      level: DEBUG
      handlers: [console, info_file_handler,error_file_handler]
# root為默認情況下的輸出配置, 當logging.getLogger("fileLoggername")裡面的fileLoggername沒有傳值的時候,
# 就是用的這個默認的root,如logging.getLogger(__name__)或logging.getLogger()
root:
    level: DEBUG
    handlers: [console]

同樣的可以通過導入 yaml 文件加載配置

with open('./loguser.yaml', 'r', encoding='utf-8') as f:
        yaml_config = yaml.load(stream=f, Loader=yaml.FullLoader)
        config.dictConfig(config=yaml_config)

    root = logging.getLogger()
    # 子記錄器的名字與配置文件中loggers字段內的保持一致
    # loggers:
    #   my_testyaml:
    #       level: DEBUG
    #       handlers: [console, info_file_handler,error_file_handler]
    my_testyaml = logging.getLogger("my_testyaml")

logging 和 print 的區別

看起來logging要比print復雜多瞭,那麼為什麼推薦在項目中使用 logging 記錄日志而不是使用print 輸出程序信息呢。

相比與print logging 具有以下優點:

  • 可以通過設置不同的日志等級,在 release 版本中隻輸出重要信息,而不必顯示大量的調試信息;
  • print 將所有信息都輸出到標準輸出中,嚴重影響開發者從標準輸出中查看其它數據;logging 則可以由開發者決定將信息輸出到什麼地方,以及怎麼輸出;
  • 和 print 相比,logging 是線程安全的。(python 3中 print 也是線程安全的瞭,而python 2中的print不是)(線程安全是指在多線程時程序不會運行混亂;而python 2 中的print 分兩步打印信息,第一打印字符串,第二打印換行符,如果在這中間發生線程切換就會產生輸出混亂。這就是為什麼python2的print不是原子操作,也就是說其不是線程安全的)印信息,第一打印字符串,第二打印換行符,如果在這中間發生線程切換就會產生輸出混亂。這就是為什麼python2的print不是原子操作,也就是說其不是線程安全的)

主要參考資料

https://blog.csdn.net/weixin_41010198/article/details/89356417

https://www.cnblogs.com/chenyibai/p/10676574.html

到此這篇關於Python 內置logging 使用詳細講的文章就介紹到這瞭,更多相關Python 內置logging內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: