1. 程式人生 > >第二十三篇 logging模塊(******)

第二十三篇 logging模塊(******)

closed out 原因 fin bubuko struct ace 更多 mon

日誌非常重要,而且非常常用,可以通過logging模塊實現。

熱身運動

import logging

logging.debug("debug message")
logging.info("info message")
logging.warning("warning message")
logging.error("error message")
logging.critical("critical message")

# 結果
# WARNING:root:warning message
# ERROR:root:error message
# CRITICAL:root:critical message
# 為社麽只顯示了三條? # 因為默認的基本是warning,會把>=warning級別的消息都打印出來。

可見,默認情況下Python的logging模塊將日誌打印到了標準輸出中,且只顯示了大於等於WARNING級別的日誌,這說明默認的日誌級別設置為WARNING(日誌級別等級CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET),默認的日誌格式為日誌級別:Logger名稱:用戶輸出消息。

測試環境下,為了調試程序,盡可能的讓開發看到更多的日誌信息,所以日誌級別會往低調;

而生產環境下,線上數據量很龐大,日誌信息有時候需要存儲很久,相應的應該調高日誌級別,沒用的日誌就不要存了,比如調到WAENING級別,甚至會跳到ERROR級別。

日誌的基本玩法

  • logging.basicConfig():日誌的基本設置
    •   level:設置rootlogger的日誌級別,不設置是,默認是WARNING級別
    •   filename:用指定的文件名創建FiledHandler,日誌會被存儲在指定的文件中。
    •   filemode:文件打開方式,在指定了filename時使用這個參數,默認值為“a”, 以追加方式打開文件;還可以指定為“w
    • format:指定handler使用的日誌顯示格式,它有以下個格式化串
      • 技術分享圖片
    •   datefmt:指定日期時間格式
    • stream:用指定的stream創建StreamHandler。(創建文件的方式是流方式還是文件方式)
      •   可以指定輸出到sys.stderr, sys.stdout 或者文件(f=open(‘test.log‘, ‘w‘)),默認為sys.stderr。
      •   若同時列出了filename和stream兩個參數,則stream參數會被忽略
      •   再說的直白點就是,如果沒有指定filename,執行後日誌信息就會顯示到屏幕下方的輸出臺上  
靈活配置日誌級別,日誌格式,輸出位置

import
logging logging.basicConfig( level=logging.DEBUG, # 把默認級別跳轉到DEBUG級別 filename = "logger.log", # 給日誌起個文件名,有這個,運行後日誌就不在顯示控制臺了 filemode="w", # 指定寫入文件時以w方式;不指定時,默認是以追加方式寫入 format="%(asctime)s %(filename)s [%(lineno)d] %(message)s", # 格式是固定的 "%(固定的字段)s" ) logging.debug("debug message") logging.info("info message") logging.warning("warning message") logging.error("error message") logging.critical("critical message")

日誌的另種高級玩法:logger對象

上述幾個例子中我們了解到了logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical()(分別用以記錄不同級別的日誌信息),logging.basicConfig()(用默認日誌格式(Formatter)為日誌系統建立一個默認的流處理器(StreamHandler),設置基礎配置(如日誌級別等)並加到root logger(根Logger)中)這幾個logging模塊級別的函數,另外還有一個模塊級別的函數是logging.getLogger([name])(返回一個logger對象,如果沒有指定名字將返回root logger)

先簡單介紹一下,logging庫提供了多個組件:Logger、Handler、Filter、Formatter:

  •   Logger對象提供應用程序可直接使用的接口,
  •   Handler發送日誌到適當的目的地,
  • Filter提供了過濾日誌信息的方法,
  • Formatter指定日誌顯示格式

一. Logger對象

Logger是一個樹形層級結構,輸出信息之前都要獲得一個Logger(如果沒有顯示的獲取則自動創建並使用root Logger,如第一個例子所示)。
logger = logging.getLogger()返回一個默認的Logger也即root Logger,並應用默認的日誌級別、Handler和Formatter設置。
當然也可以通過Logger.setLevel(lel)指定最低的日誌級別,可用的日誌級別有logging.DEBUG、logging.INFO、logging.WARNING、logging.ERROR、logging.CRITICAL。
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical()輸出不同級別的日誌,只有日誌等級大於或等於設置的日誌級別的日誌才會被輸出。

import logging
def log():
    # 首先得創建一個對象
    logger = logging.getLogger()

    fh = logging.FileHandler("test_log")    #  向文件發送內容
    ch = logging.StreamHandler()  #  向屏幕發送內容

    # fh 和 ch 都有自己默認的日誌格式,但是也可以修改他們的日誌格式
    fm = logging.Formatter("%(asctime)s %(message)s")
    fh.setFormatter(fm)
    # ch.setFormatter(fm)  # 如果註釋了這行,ch還存在,而且還有自己默認的格式,屏幕依然會顯示日誌

    logger.addHandler(fh)
    logger.addHandler(ch)
    logger.setLevel("DEBUG")
    return logger    # 返回一個logger對象

# ========================================
# 上面都是對logger的設置,而且設置成了函數,在任何地方都可以調用了
###下面都是操作logger###
logger = log()
logger.debug("hello debug")
logger.info("hello info")
logger.warning("hello warning")
logger.error("hello error")
logger.critical("hello critical")

兩個需要註意的地方:

(1) 創建兩個logger對象,logger1 和 logger2。

import logging

logger1 = logging.getLogger("myLogger")
logger1.setLevel(logging.DEBUG)

logger2 = logging.getLogger("myLogger")
logger2.setLevel(logging.INFO)

fh=logging.FileHandler("new_logger_test")
ch = logging.StreamHandler()

logger1.addHandler(fh)
logger1.addHandler(ch)

logger2.addHandler(fh)
logger2.addHandler(ch)


logger1.debug("logger1 hello debug")
logger1.info("logger1 hello info")
logger1.warning("logger1 hello warning")
logger1.error("logger1 hello error")
logger1.critical("logger1 hello critical")


logger2.debug("logger2 hello debug")
logger2.info("logger2 hello info")
logger2.warning("logger2 hello warning")
logger2.error("logger2 hello error")
logger2.critical("logger2 hello critical")


# 運行結果
技術分享圖片

這裏是有問題的啦:

(1)logger1的level是DEBUG級別,應該顯示5條日誌,實際上顯示了4條,從INFO級別開始顯示的,為什麽沒有顯示出DEBUG級別的日誌信息?

(2)logger2的level是INFO級別,應該顯示4條日誌,實際也顯示了4條

原因:

原來logger1和logger2對應的是同一個Logger實例,只要logging.getLogger(name)中名稱參數name相同則返回的Logger實例就是同一個,且僅有一個,也即name與Logger實例一一對應。在logger2實例中通過logger2.setLevel(logging.INFO)設置mylogger的日誌級別為logging.INFO,所以最後logger1的輸出遵從了後來設置的日誌級別。

技術分享圖片

所以,結論是:如果要父對象logger下要創建多個logger對象,name一定不能一樣,否則就會出現類似上面的問題

(2)

# 1. 糾正上面的問題,把name改為不一致

import
logging logger = logging.getLogger() # 2.創建一個根對象 logger.setLevel(logging.WARN) logger1 = logging.getLogger("myLogger1") # name名修改 logger1.setLevel(logging.DEBUG) logger2 = logging.getLogger("myLogger2")
 # name名修改
logger2.setLevel(logging.INFO) fh=logging.FileHandler("new_logger_test") ch = logging.StreamHandler() logger.addHandler(ch) logger.addHandler(fh) logger1.addHandler(fh) logger1.addHandler(ch) logger2.addHandler(fh) logger2.addHandler(ch) logger.debug("logger hello debug") logger.info("logger hello info") logger.warning("logger hello warning") logger.error("logger hello error") logger.critical("logger hello critical") logger1.debug("logger1 hello debug") logger1.info("logger1 hello info") logger1.warning("logger1 hello warning") logger1.error("logger1 hello error") logger1.critical("logger1 hello critical") logger2.debug("logger2 hello debug") logger2.info("logger2 hello info") logger2.warning("logger2 hello warning") logger2.error("logger2 hello error") logger2.critical("logger2 hello critical") # 結果
技術分享圖片

從結果中看,首先上面的問題通過修改name後,就解決了。

而同時創建了一個根logger,又帶來另外一個新問題,導致logger1 和logger2都顯示了2遍,這是為什麽?

原因:

(1)這是因為我們通過logger = logging.getLogger()顯示的創建了root Logger,

  (2)而logger1 = logging.getLogger(‘mylogger1‘)創建了root Logger的孩子(root.)mylogger1,

  (3)logger2與logger1 同樣,也創建了root Logger的孩子(root.)mylogger2。

  (4)而孩子,孫子,重孫……既會將消息分發給他的handler進行處理,而且也會傳遞給所有的祖先Logger處理,所以父logger打印了自己的一次外,也處理了來自子孫的mylogger1和mylogger2,又打印了1次,再加上子孫mylogger1和mylogger2自己也會打印1次,所以加起來mylogger1和mylogger2都分別打印了2次。

OK。

ok,那麽現在我們把下面兩行註釋掉

# logger.addHandler(fh)

# logger.addHandler(ch) 註釋掉,我們再來看效果:

技術分享圖片

為什麽這次對了呢?

原因:

  (1)因為我們註釋了logger對象顯示的位置,所以才用了默認方式,即標準輸出方式。因為它的父級沒有設置文件顯示方式,所以在這裏只打印了一次。

  (2)孩子,孫子,重孫……可逐層繼承來自祖先的日誌級別、Handler、Filter設置,也可以通過Logger.setLevel(lel)、Logger.addHandler(hdlr)、Logger.removeHandler(hdlr)、Logger.addFilter(filt)、Logger.removeFilter(filt)。設置自己特別的日誌級別、Handler、Filter。若不設置則使用繼承來的值。

二. Filter()

限制只有滿足過濾規則的日誌才會輸出。
比如我們定義了filter = logging.Filter(‘a.b.c‘),並將這個Filter添加到了一個Handler上,則使用該Handler的Logger中只有名字帶 a.b.c前綴的Logger才能輸出其日誌。

filter = logging.Filter(‘mylogger‘)

logger.addFilter(filter)

這是只對logger這個對象進行篩選,

如果想對所有的對象進行篩選,則:

filter = logging.Filter(‘mylogger‘)

fh.addFilter(filter)

ch.addFilter(filter)

這樣,所有添加fh或者ch的logger對象都會進行篩選。

完整代碼1:

技術分享圖片
import logging

logger = logging.getLogger()
# 創建一個handler,用於寫入日誌文件
fh = logging.FileHandler(test.log)

# 再創建一個handler,用於輸出到控制臺
ch = logging.StreamHandler()

formatter = logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s)

fh.setFormatter(formatter)
ch.setFormatter(formatter)

# 定義一個filter
filter = logging.Filter(mylogger)
fh.addFilter(filter)
ch.addFilter(filter)

# logger.addFilter(filter)
logger.addHandler(fh)
logger.addHandler(ch)




logger.setLevel(logging.DEBUG)

logger.debug(logger debug message)
logger.info(logger info message)
logger.warning(logger warning message)
logger.error(logger error message)
logger.critical(logger critical message)

##################################################
logger1 = logging.getLogger(mylogger)
logger1.setLevel(logging.DEBUG)

logger2 = logging.getLogger(mylogger)
logger2.setLevel(logging.INFO)

logger1.addHandler(fh)
logger1.addHandler(ch)

logger2.addHandler(fh)
logger2.addHandler(ch)

logger1.debug(logger1 debug message)
logger1.info(logger1 info message)
logger1.warning(logger1 warning message)
logger1.error(logger1 error message)
logger1.critical(logger1 critical message)

logger2.debug(logger2 debug message)
logger2.info(logger2 info message)
logger2.warning(logger2 warning message)
logger2.error(logger2 error message)
logger2.critical(logger2 critical message)
View Code

完整代碼2:

技術分享圖片
#coding:utf-8  
import logging  
  
# 創建一個logger    
logger = logging.getLogger()  
  
logger1 = logging.getLogger(mylogger)  
logger1.setLevel(logging.DEBUG)  
  
logger2 = logging.getLogger(mylogger)  
logger2.setLevel(logging.INFO)  
  
logger3 = logging.getLogger(mylogger.child1)  
logger3.setLevel(logging.WARNING)  
  
logger4 = logging.getLogger(mylogger.child1.child2)  
logger4.setLevel(logging.DEBUG)  
  
logger5 = logging.getLogger(mylogger.child1.child2.child3)  
logger5.setLevel(logging.DEBUG)  
  
# 創建一個handler,用於寫入日誌文件    
fh = logging.FileHandler(/tmp/test.log)  
  
# 再創建一個handler,用於輸出到控制臺    
ch = logging.StreamHandler()  
  
# 定義handler的輸出格式formatter    
formatter = logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s)  
fh.setFormatter(formatter)  
ch.setFormatter(formatter)  
  
#定義一個filter  
#filter = logging.Filter(‘mylogger.child1.child2‘)  
#fh.addFilter(filter)    
  
# 給logger添加handler    
#logger.addFilter(filter)  
logger.addHandler(fh)  
logger.addHandler(ch)  
  
#logger1.addFilter(filter)  
logger1.addHandler(fh)  
logger1.addHandler(ch)  
  
logger2.addHandler(fh)  
logger2.addHandler(ch)  
  
#logger3.addFilter(filter)  
logger3.addHandler(fh)  
logger3.addHandler(ch)  
  
#logger4.addFilter(filter)  
logger4.addHandler(fh)  
logger4.addHandler(ch)  
  
logger5.addHandler(fh)  
logger5.addHandler(ch)  
  
# 記錄一條日誌    
logger.debug(logger debug message)  
logger.info(logger info message)  
logger.warning(logger warning message)  
logger.error(logger error message)  
logger.critical(logger critical message)  
  
logger1.debug(logger1 debug message)  
logger1.info(logger1 info message)  
logger1.warning(logger1 warning message)  
logger1.error(logger1 error message)  
logger1.critical(logger1 critical message)  
  
logger2.debug(logger2 debug message)  
logger2.info(logger2 info message)  
logger2.warning(logger2 warning message)  
logger2.error(logger2 error message)  
logger2.critical(logger2 critical message)  
  
logger3.debug(logger3 debug message)  
logger3.info(logger3 info message)  
logger3.warning(logger3 warning message)  
logger3.error(logger3 error message)  
logger3.critical(logger3 critical message)  
  
logger4.debug(logger4 debug message)  
logger4.info(logger4 info message)  
logger4.warning(logger4 warning message)  
logger4.error(logger4 error message)  
logger4.critical(logger4 critical message)  
  
logger5.debug(logger5 debug message)  
logger5.info(logger5 info message)  
logger5.warning(logger5 warning message)  
logger5.error(logger5 error message)  
logger5.critical(logger5 critical message)
View Code

應用:

技術分享圖片
import os
import time
import logging
from config import settings


def get_logger(card_num, struct_time):

    if struct_time.tm_mday < 23:
        file_name = "%s_%s_%d" %(struct_time.tm_year, struct_time.tm_mon, 22)
    else:
        file_name = "%s_%s_%d" %(struct_time.tm_year, struct_time.tm_mon+1, 22)

    file_handler = logging.FileHandler(
        os.path.join(settings.USER_DIR_FOLDER, card_num, record, file_name),
        encoding=utf-8
    )
    fmt = logging.Formatter(fmt="%(asctime)s :  %(message)s")
    file_handler.setFormatter(fmt)

    logger1 = logging.Logger(user_logger, level=logging.INFO)
    logger1.addHandler(file_handler)
    return logger1
View Code

第二十三篇 logging模塊(******)