1. 程式人生 > 實用技巧 >Python筆記 logging模組

Python筆記 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(日誌級別等級CRITICAL > ERROR > WARNING >INFO > DEBUG > NOTSET),預設的日誌格式為日誌級別:Logger名稱:使用者輸出訊息。

二 配置預設級別

import logging  
logging.basicConfig(level=logging.DEBUG,  
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',  
                    datefmt='%a, %d %b %Y %H:%M:%S',  
                    filename='/tmp/test.log',  
                    filemode
='a') logging.debug('debug message') logging.info('info message') logging.warning('warning message') logging.error('error message') logging.critical('critical message')

輸出

Mon, 05 May 2014 16:29:53 test_logging.py[line:9] DEBUG debug message
Mon, 05 May 2014 16:29:53 test_logging.py[line:10] INFO info message

Mon, 05 May 2014 16:29:53 test_logging.py[line:11] WARNING warning message
Mon, 05 May 2014 16:29:53 test_logging.py[line:12] ERROR error message
Mon, 05 May 2014 16:29:53 test_logging.py[line:13] CRITICAL critical message

logging.basicConfig()函式中可通過具體引數來更改logging模組預設行為,可用引數有
filename:用指定的檔名建立FiledHandler(後邊會具體講解handler的概念),這樣日誌會被儲存在指定的檔案中。
filemode:檔案開啟方式,在指定了filename時使用這個引數,預設值為“a”還可指定為“w”。
format:指定handler使用的日誌顯示格式。
datefmt:指定日期時間格式。
level:設定rootlogger(後邊會講解具體概念)的日誌級別
stream:用指定的stream建立StreamHandler。可以指定輸出到sys.stderr,sys.stdout或者檔案(f=open('test.log','w')),預設為sys.stderr。若同時列出了filename和stream兩個引數,則stream引數會被忽略。

format引數中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 數字形式的日誌級別
%(levelname)s 文字形式的日誌級別
%(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物件

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)

logger.addHandler(fh) #logger物件可以新增多個fh和ch物件
logger.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')

logging庫提供了多個元件:Logger、Handler、Filter、Formatter。Logger物件提供應用程式可直接使用的介面,Handler傳送日誌到適當的目的地,Filter提供了過濾日誌資訊的方法,Formatter指定日誌顯示格式。

(1)

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()輸出不同級別的日誌,只有日誌等級大於或等於設定的日誌級別的日誌才會被輸出。

輸出

2014-05-06 12:54:43,222 - root - WARNING - logger warning message
2014-05-06 12:54:43,223 - root - ERROR - logger error message
2014-05-06 12:54:43,224 - root - CRITICAL - logger critical message
從這個輸出可以看出logger = logging.getLogger()返回的Logger名為root。這裡沒有用logger.setLevel(logging.Debug)顯示的為logger設定日誌級別,所以使用預設的日誌級別WARNIING,故結果只輸出了大於等於WARNIING級別的資訊。

  (2) 如果我們再建立兩個logger物件:

##################################################
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')

結果

2020-08-02 08:43:37,519 - root - WARNING - logger warning message
2020-08-02 08:43:37,519 - root - ERROR - logger error message
2020-08-02 08:43:37,520 - root - CRITICAL - logger critical message
2020-08-02 08:43:37,521 - mylogger - INFO - logger1 info message
2020-08-02 08:43:37,521 - mylogger - INFO - logger1 info message
2020-08-02 08:43:37,523 - mylogger - WARNING - logger1 warning message
2020-08-02 08:43:37,523 - mylogger - WARNING - logger1 warning message
2020-08-02 08:43:37,523 - mylogger - ERROR - logger1 error message
2020-08-02 08:43:37,523 - mylogger - ERROR - logger1 error message
2020-08-02 08:43:37,524 - mylogger - CRITICAL - logger1 critical message
2020-08-02 08:43:37,524 - mylogger - CRITICAL - logger1 critical message
2020-08-02 08:43:37,524 - mylogger - INFO - logger2 info message
2020-08-02 08:43:37,524 - mylogger - INFO - logger2 info message
2020-08-02 08:43:37,531 - mylogger - WARNING - logger2 warning message
2020-08-02 08:43:37,531 - mylogger - WARNING - logger2 warning message
2020-08-02 08:43:37,532 - mylogger - ERROR - logger2 error message
2020-08-02 08:43:37,532 - mylogger - ERROR - logger2 error message
2020-08-02 08:43:37,536 - mylogger - CRITICAL - logger2 critical message
2020-08-02 08:43:37,536 - mylogger - CRITICAL - logger2 critical message

這裡有兩個個問題:

<1>我們明明通過logger1.setLevel(logging.DEBUG)將logger1的日誌級別設定為了DEBUG,為何顯示的時候沒有顯示出DEBUG級別的日誌資訊,而是從INFO級別的日誌開始顯示呢?

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

<2>為什麼logger1、logger2對應的每個輸出分別顯示兩次?
這是因為我們通過logger = logging.getLogger()顯示的建立了root Logger,而logger1 = logging.getLogger('mylogger')建立了root Logger的孩子(root.)mylogger,logger2同樣。而孩子,孫子,重孫……既會將訊息分發給他的handler進行處理也會傳遞給所有的祖先Logger處理。

ok,那麼現在我們把

# logger.addHandler(fh)

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

logger warning message
logger error message
logger critical message
2020-08-02 08:45:01,367 - mylogger - INFO - logger1 info message
2020-08-02 08:45:01,367 - mylogger - WARNING - logger1 warning message
2020-08-02 08:45:01,367 - mylogger - ERROR - logger1 error message
2020-08-02 08:45:01,368 - mylogger - CRITICAL - logger1 critical message
2020-08-02 08:45:01,368 - mylogger - INFO - logger2 info message
2020-08-02 08:45:01,368 - mylogger - WARNING - logger2 warning message
2020-08-02 08:45:01,368 - mylogger - ERROR - logger2 error message
2020-08-02 08:45:01,369 - mylogger - CRITICAL - logger2 critical message

因為我們註釋了logger物件顯示的位置,所以才用了預設方式,即標準輸出方式

因為它的父級沒有設定檔案顯示方式,所以在這裡只打印了一次。

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

<3>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')

完整程式碼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')

應用:

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