logging的理解和使用一

2020-08-08 19:20:39

#參考官方文件----

一、logging基本使用

1、logging概述

logging是軟體執行過程中跟蹤一些時間發生的一種手段,軟件開發會在軟體的一些特定時間發生後在程式碼中新增log,此時會呼叫logging。日誌裏面對這個事件資訊進行描述,可以包含一些變數數據,具體資訊由自己定義。這些事件也存在一定的嚴重程度,這些也是由開發者賦給事件的嚴重性,即會有一個時間的Level表示不同嚴重級別的事件。

2、什麼時候會用到logging

logging提供了一系列的functions供使用,例如debug()、info()、warnning()、error()、critical().下述描述了什麼時候用logging.

想要執行的任務 該任務最適合的工具
一個命令列指令碼或者程式的一般用法顯示在console輸出 print()
正常程式執行過程中的一些事件的觸發記錄 loggiing.info()、logging.debug()
觸發了一個報警事件 warnings.warn()-如果需要client對數據進行處理或者改變;loging.waring()-client可以不做任何處理,這是一個告警提醒使用者注意
在執行過程中觸發error Raise 觸發一個異常
報告一個異常error但是不觸發 logging.error()、logging.exception()、logging.critcal

logging的方法使用之前需要定義log的嚴重級別來確定需要跟蹤的事件,下述表格描述標準的級別定義。(可以重寫方法新增其他級別)

Level 什麼時候使用
DEBUG 詳細資訊,只有診斷問題時才需要,就是一般的偵錯資訊
INFO 當程式執行時期望的一些資訊
WARNING 軟體執行正常,但是可能會有一些預期之外的事件發生
ERROR 由於一些嚴重問題導致軟體一些功能出現問題
CRITICAL 很嚴重的錯誤直接導致軟體不能繼續執行

預設的級別時WARNING,意味預設的級別下,顯示的日誌資訊必須時>=WARNING的級別纔會進行處理。
日誌的記錄可以由不同的方式,最簡單的是把日誌直接輸出到console,另外一種就是可以把他們寫到檔案。

舉一個簡單的例子:

import logging
logging.warning('Watch out!')
logging.info("I told you so")

執行結果:

WARNING:root:Watch out !

列印到console,INFO資訊沒有列印是因爲預設的日誌級別是WARNING。當前列印的資訊包含級別:loggger 名字:日誌資訊。(可以設定格式和修改logger name.後面會介紹)

儲存日誌到檔案

比較好的方法是把日誌列印在一個檔案,後續可以檢視。

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug("This message should go to the log file")
logging.info("So should this")
logging.warning("And this,too")

執行上述程式碼可以在當前目錄找到example.log檔案,檔案內容如下:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this,too

上述例子告訴我們如何設定日誌級別,設定級別爲INFO,所以DEBUG和WARNING比它級別高的日誌都可以收集到。
如果執行多次上述的指令碼,則每一次執行的日誌都會追加到example.log,如果想要不記錄之前的執行日誌,需要設定filemode參數:
logging.basicConfig(filename=‘examle.log’,level=logging.DEBUG,filemode=‘w’)
這樣設定後面再執行直接覆蓋前面執行的內容。

多個模組的日誌處理

如果程式跑了多個模組,需要處理logging,下述一個例子告訴我們怎麼處理這種情況:

#myapp.py
import logging
import mylib
def main():
	logging.basicConfig(filename='myaoo.log',level=logging.INFO)
	logging.info("start")
	mylib.do_something()
	logging.info("Finished!")
if __name__=='__main__':
	main()
#mylib.py
def do_something()
	logging.info("Doing something!")
	

執行myapp.py,mypp.log內容如下:

INFO:root:start
INFO:root:Doing something!
INFO:root:Finished!

新增變數logging

日誌記錄事件,有時會需要記錄一些數據,
import logging
logging.warning(’%s before you %s’,‘Look’,‘leap!’)
日誌展現如下:
WARNING:root:Look before you leap!
如上可以看出加入變數的值就用%s即可

修改資訊在日誌中的格式

修改日誌展示的格式:

Import logging
logging.basicConfig(format='%(levelname)s:%(meaasge)s',level=logging.DEBUG)
logging.debug("This message should appear on the console")
logging.info('So should this')
logging.warning('And this,too')

執行結果:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this,too

上述的參數可以自己新增,常用參數由:
%(acstime)s 時間
%(filename)s 日誌檔名
%(funcName)s 呼叫日誌的函數名
%(levelname)s 日誌的級別
%(module)s 呼叫日誌的模組名
%(message)s 日誌資訊
%(name)s logger的name,不寫的話預設是root
-還有一些其他的,用到的話可以參考官方文件

執行緒安全

logging模組不需要呼叫它的使用者再做其他的操作使執行緒安全,它自己已經實現了執行緒鎖,每一個模組呼叫的時候是鎖定了共用數據區域,每一個handler也對IO進行加鎖操作。這裏瞭解的不多,暫時先這樣。

模組級別功能

logging框架中主要由四個部分組成:

Loggers: 可供程式直接呼叫的介面
Handlers: 決定將日誌記錄分配至正確的目的地
Filters: 提供更細粒度的日誌是否輸出的判斷
Formatters: 制定最終記錄列印的格式佈局

logging.getLogger(name=None)返回一個loggger的物件,這個物件是提供所有的log使用的介面,name如果未None則使用的是root logger,否則可自己定義一個自己的名字。
所有呼叫日誌的介面,如果logger name一樣則都是同一個logger範例,不需要在不同的應用中傳輸。
logging.getLoggerClass()
返回一個標準的logger類,或者是傳輸給setLoggerClass()的類。可以新建logger類,然後繼承該類。
logging.basicConfig(**kwargs)
對logging做一些基本的設定,建立StreamHandler,預設的Formatter,增加這些到rootlogger.

支援參數如下:
filename—日誌儲存檔案路徑
filemode–寫日誌的方式,預設是a即追加模式,w爲覆蓋填寫
format–日誌格式
datefmt–時間格式
style–format如果是特別的,要用特定的fotmat格式,預設是%
level–日誌過濾的級別
stream–用特定的stream初始化StreamHandler,如果有filename該參數無效
handlers–filename or stream和該參數衝突,否則會引起錯誤

----其他的功能請參考官方文件

使用方法1-logger使用,設定handler

建立一個logger,定義handlers(stream或者file),對stream和file進行格式新增以及logging.level的新增,接着把handler新增到logger。
在需要新增log的地方呼叫logging.info()、logging.debug()的等加入自己需要的日誌。舉例如下:

import logging
#建立logger
log=logging.getLogger("example")
log.setLevel(logging.DEBUG)
hander1=logging.StreamHandler()
hander2=logging.FileHandler('haha.log',mode='w')
hander1.setLevel(logging.INFO)
hander2.setLevel(logging.DEBUG)
formatter1=logging.Formatter("%(asctime)s-%(name)s-%(levelname)s-%(message)s")
formatter2=logging.Formatter("%(levelname)s : %(message)s")
hander1.setFormatter(formatter1)
hander2.setFormatter(formatter2)
log.addHandler(hander1)
log.addHandler(hander2)
log.info("這是一個info資訊")
log.debug("這是一個debug資訊")
log.warning("這是一個warnning資訊")
log.info("這個怎麼算呢")

執行結果:

C:\Users\18566\AppData\Local\Programs\Python\Python38\python.exe C:/Users/18566/Desktop/APPAutoTest/logginglearn/loggingUse.py
2020-08-07 18:13:46,190-example-INFO-這是一個info資訊
2020-08-07 18:13:46,191-example-WARNING-這是一個warnning資訊
2020-08-07 18:13:46,191-example-INFO-這個怎麼算呢

Process finished with exit code 0


haha.log內容:
INFO : 這是一個info資訊
DEBUG : 這是一個debug資訊
WARNING : 這是一個warnning資訊
INFO : 這個怎麼算呢

檢視整體的loggig定義的level級別一定要比handler自己的定義levle低,否則日誌會以logging的level進行日誌的過濾。

使用方法2—logging.basicConfig()

先看一個程式:

import logging
print(logging._handlerList)
logging.basicConfig(level=logging.INFO,format='%(asctime)s:%(name)s:%(levelname)s:%(message)s')
print(logging._handlerList)
logger1=logging.FileHandler(filename='now.log',mode='w')
print(logger1.setFormatter())
logger2=logging.StreamHandler()
logger1.setLevel(logging.WARNING)
logger2.setLevel(logging.DEBUG)
logging.getLogger().addHandler(logger1)
logging.getLogger().addHandler(logger2)
print(logging._handlerList)
print("+++++++++++++++++++")
logging.info("這是一個logging")
logging.warning("這個號碼")
logging.error("error級別日誌應該都可以獲取")
logger1_log=logging.getLogger("logger1_log")
logger2_log=logging.getLogger("logger2_log")
print("+++++++++++++++++++++++")
print(logging._handlerList)
logger1_log.info("這個時logger1 info")
logger2_log.debug("這個時logger2 debug")
logger2_log.error("這個時logger2 error")
logging.error("放在最後的logging error")

執行結果如下:

C:\Users\18566\AppData\Local\Programs\Python\Python38\python.exe C:/Users/18566/Desktop/APPAutoTest/logginglearn/loggingUse.py
2020-08-08 10:25:20,403:root:INFO:這是一個logging
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>]
這是一個logging
2020-08-08 10:25:20,403:root:WARNING:這個號碼
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to 'StreamHandler' at 0x00000255A9A3B6D0>]
這個號碼
None
2020-08-08 10:25:20,403:root:ERROR:error級別日誌應該都可以獲取
error級別日誌應該都可以獲取
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to 'StreamHandler' at 0x00000255A9A3B6D0>, <weakref at 0x00000255A9CC8DB0; to 'FileHandler' at 0x00000255A9CC1BB0>, <weakref at 0x00000255A9CDE860; to 'StreamHandler' at 0x00000255A9CC1EE0>]
2020-08-08 10:25:20,403:logger1_log:INFO:這個時logger1 info
+++++++++++++++++++
+++++++++++++++++++++++
這個時logger1 info
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to 'StreamHandler' at 0x00000255A9A3B6D0>, <weakref at 0x00000255A9CC8DB0; to 'FileHandler' at 0x00000255A9CC1BB0>, <weakref at 0x00000255A9CDE860; to 'StreamHandler' at 0x00000255A9CC1EE0>]
2020-08-08 10:25:20,403:logger2_log:ERROR:這個時logger2 error
這個時logger2 error
2020-08-08 10:25:20,403:root:ERROR:放在最後的logging error
放在最後的logging error

Process finished with exit code 0

now.log內容:
這個號碼
error級別日誌應該都可以獲取
這個時logger2 error
放在最後的logging error

程式分析:
1、關於輸出格式,如果對handler沒有進行格式的設定,則有一個預設格式,檢視logging裏面的原始碼資訊

class Formatter(object):
    """
    Formatter instances are used to convert a LogRecord to text.

    Formatters need to know how a LogRecord is constructed. They are
    responsible for converting a LogRecord to (usually) a string which can
    be interpreted by either a human or an external system. The base Formatter
    allows a formatting string to be specified. If none is supplied, the
    the style-dependent default value, "%(message)s", "{message}", or
    "${message}", is used.

    The Formatter can be initialized with a format string which makes use of
    knowledge of the LogRecord attributes - e.g. the default value mentioned
    above makes use of the fact that the user's message and arguments are pre-
    formatted into a LogRecord's message attribute. Currently, the useful
    attributes in a LogRecord are described by:

    %(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
    """

解釋語句有一句話:the style-dependent default value, 「%(message)s」, 「{message}」, or
「${message}」, is used.
所以在上述輸出有一個單獨的message資訊,這是因爲不管是logger1還是logger2都沒有對格式做設定,預設的格式就是隻輸出message.
2、爲什麼還輸出了一個帶格式的日誌呢?
看到程式中輸出的handlersList,最開始沒有新增任何handler時,預設時有一個標準的錯誤輸出的:[<weakref at 0x00000255A9D888B0; to ‘_StderrHandler’ at 0x00000255A9D7A970>]

設定了logging.basciConfig(),沒有定義任何的handler,logging.basciConfig如果沒有說明handler時console還是file則預設是streamHandler,所以此時的handlerList變爲:
[<weakref at 0x00000255A9D888B0; to ‘_StderrHandler’ at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to ‘StreamHandler’ at 0x00000255A9A3B6D0>]
3、增加了2個handler之後,HandlerList變爲:
[<weakref at 0x00000255A9D888B0; to ‘_StderrHandler’ at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to ‘StreamHandler’ at 0x00000255A9A3B6D0>, <weakref at 0x00000255A9CC8DB0; to ‘FileHandler’ at 0x00000255A9CC1BB0>, <weakref at 0x00000255A9CDE860; to ‘StreamHandler’ at 0x00000255A9CC1EE0>]
分別新增了一個streamHandler和一個FileHandler,所以fie裏面會寫一次不帶格式的日誌。因爲handlerList只新增了一個fileHandler且沒有定義格式。有2個streamHandler,一個是在logging.BasicConfig中定義了格式,logger2 streamHandler採用預設格式,所以日誌輸入格式:

2020-08-08 10:46:21,428:root:ERROR:放在最後的logging error
放在最後的logging error

4、增加了2個logger的名字,可以看出所有的handler會接受所有的logger的日誌進行track,所以root,logger1_log,logger2-log都會寫到log和cosole

日誌參數採用檔案設定

匯入庫檔案logging.config

def fileConfig(fname, defaults=None, disable_existing_loggers=True):
    """
    Read the logging configuration from a ConfigParser-format file.

    This can be called several times from an application, allowing an end user
    the ability to select from various pre-canned configurations (if the
    developer provides a mechanism to present the choices and load the chosen
    configuration).
    """
    import configparser

    if isinstance(fname, configparser.RawConfigParser):
        cp = fname
    else:
        cp = configparser.ConfigParser(defaults)
        if hasattr(fname, 'readline'):
            cp.read_file(fname)
        else:
            cp.read(fname)

    formatters = _create_formatters(cp)

    # critical section
    logging._acquireLock()
    try:
        _clearExistingHandlers()

        # Handlers add themselves to logging._handlers
        handlers = _install_handlers(cp, formatters)
        _install_loggers(cp, handlers, disable_existing_loggers)
    finally:
        logging._releaseLock()

用configparser解析ini檔案,ini檔案的寫法如下:

logging.ini
[loggers]
keys=root,example
[handlers]
keys=consoleHandler,FileHandler
[formatters]
keys=consoleFmt,fileFmt
[formatter_consoleFmt]
format=%(asctime)s::%(levelname)s::%(name)s::%(message)s
[formatter_fileFmt]
format=%(name)s::%(levelname)s::%(message)s
[logger_root]
level=DEBUG
handlers=consoleHandler,FileHandler
[logger_example]
handlers=consoleHandler
qualname=example
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=consoleFmt
args=(sys.stdout,)
[handler_FileHandler]
class=FileHandler
level=DEBUG
formatter=fileFmt
args=('./wwxxyy.log','w')

指令碼如下:

import logging.config
logging.config.fileConfig('./logging.ini')
logging.info("this is a info log")
logging.debug("this is s debug log")
logging.warning("this is a warning log")
logging.error("this is a error log")
#
logger=logging.getLogger("example")
logger.error("hahhahhah")

執行結果是:

C:\Users\18566\AppData\Local\Programs\Python\Python38\python.exe C:/Users/18566/Desktop/APPAutoTest/logginglearn/loggingUse.py
2020-08-08 18:48:55,391::INFO::root::this is a info log
2020-08-08 18:48:55,391::WARNING::root::this is a warning log
2020-08-08 18:48:55,391::ERROR::root::this is a error log
2020-08-08 18:48:55,391::ERROR::example::hahhahhah

Process finished with exit code 0

wwwxxxyyy.log內容如下:

root::INFO::this is a info log
root::DEBUG::this is s debug log
root::WARNING::this is a warning log
root::ERROR::this is a error log

具體實現還要繼續看原始碼,目前還沒看明白原始碼,先記錄下怎麼實現把。
ini組態檔是用configParser讀取出section option然後做一堆處理的。
ini檔案包含的內容分如下幾部分:
1、先要寫你要的logger,section='loggers ’ option=‘keys’,keys的內容寫你需要傳入的logger name.

[loggers]
keys=root,example

2、要寫傳入的數據的目的地,目前用的多的streamHandler ,fileHandler,其他的請參考官方文件。handlers這裏的名字可以隨便寫,爲了方便讀寫當然最好是見詞知意。

[handlers]
keys=consoleHandler,fileHandler

3、像之前採用basicConfig也是需要加fmt的,當然不加的話就是預設格式

[formatters]
keys=fmt1,fmt2

4、上述指定的數據,下述對數據進行設定,先設定fmt,其實不分先後 先後,只要在檔案能找到即可

[formatter_fmt1]
format=(%asctime)s::::%(levelname)s::%(name)s:%(message)s
[formatter_fmt2]
format=(%levelname)s:%(name)s:%(message)s

5、指定handler設定,指定每個handler的參數

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=fmt1
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=INFO
formatter=fmt2
args=("log.log","w")

6、新增到logger,新增handler到logger

[logger_root]
handlers=consoleHandler,fileHandler
level=DEBUG
[logger_example]
handlers=condoleHandler
level=INFO
qualname=example
propagate=1

qualname=example
propagate=0
加2個參數的原因:
對於跟記錄器以外的記錄器要新增qualname和propagate零個參數。
級別和處理程式條目解釋爲跟記錄器,除非如果非根記錄器的級別被指定爲NOTSET,則系統會在層次結構更高的層次上諮詢記錄器以確定記錄器的有效級別。傳播條目設定爲1(propagate)以指示訊息必須傳播到記錄器層次結構中自此記錄器的處理程式,或者0表示訊息不傳播到層次結構中的處理程式。品質名qualname是記錄器的分層通道名稱,也就是應用程式用來獲取記錄器的名稱。

這一段解釋感覺非常拗口,個人理解:
如果不設定logger或者設定爲‘’(實際也是root),記錄器相當於沒有分層,就只記錄root 名字的logger的內容。如果設定了其他的logger name,記錄器需要分層處理,設定的其他logger name設定區分不同的分層,當然還帶了一個分層不分層的標記propagate標記,1爲分層,0不分層。–個人理解,僅供參考

先寫到這裏把,後續有進步再追加!!!!!!!!