python 中得logging系統

1. logging模塊中基本的幾個類

logging模塊中類繼承關係 

圖中是涉及到的類的繼承關係,下面挑一些關鍵類進行講解。

LogRecord

    這是一個記錄日誌信息的類,構造這樣的一個對象,依據其__init__(self, name, level, pathname, lineno,msg, args, exc_info, func=None)需要傳入的參數依次是生成該條日誌logger的名字,該條日誌的等級,輸出該日誌語句所在文件的路徑,輸出該日誌語句所在的行號,日誌字符串,參數,異常信息以及輸出該日誌語句所在的函數!其中args是tuple形式,元素可以是基本類型,比如int,float,string,也可以是dict。舉個例子,msg = "a %(a)d b %(b)s",args = ({'a':1, 'b':2})。爲了方便應用(構造Formatter以及Filter),介紹一下該類中的幾個成員變量:

變量名

描述

構造Formatter串對應項

self.name

生成該日誌logger的名字

%(name)s

self.levelno

日誌等級的數值表示

%(levelno)s

self.levelname

日誌等級的字符串表示

%(levelname)s

self.pathname

輸出該日誌其語句所在文件的路徑

%(pathname)s

self.filename

self.pathname中得文件名

%(filename)s

self.module

輸出該日誌其語句所在的模塊名

%(module)s

self.lineno

輸出該日誌語句其語句所在的行號

%(lineno)d 

self.funcName

輸出該日誌其語句所在的函數

%(funcName)s

self.created

日誌被創建的時間(Epoch時間,time.time()的返回值)

%(created)f

self.msecs

self.created的小數部位乘以1000(毫秒單位)

%(msecs)d

self.relativeCreated

日誌被創建時間與日誌模塊被加載時間的時間差(毫秒單位)

%(relativeCreated)d

self.thread

創建該日誌線程的ID

%(thread)d

self.threadName

創建該日誌線程的名字

%(threadName)s

self.process

創建該日誌進程的ID

%(process)d

self.processName

創建該日誌進程的名字

%(processName)s(文檔裏並沒有給出,似乎是由於logging不支持多進程訪問一個文件【3】,意味着沒意義?)

    還有另外兩個不應該直接輸出的變量(是不應該,不是不能),一個是self.msg, 一個是self.args. 前者直接等於構造對象時傳入的參數msg,後者則對傳入的參數進行了點改造:如果是基本元素的tuple,直接賦值args;如果是dict的tuple,相應的dict賦值給args。 LogRecord類的getMessage函數主要就是整合這兩個變量,返回一個字符串。你可以構造Formatter字符串時使用%(message)s 輸出這個字符串。

    可以使用str(LogRecord對象)查看具體的信息,具體輸出像這樣:

    '<LogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno,self.pathname, self.lineno, self.msg)

Formatter

其初始化函數爲 __init__(self, fmt=None, datefmt=None),其中fmt爲包含LogRecord中提到的“構造Formatter串對應項”的日誌格式串,例如,“%(name)s in the thread %(threadName)s”。其默認值爲”%s(message)\\n“。datefmt 爲一個時間格式串,具體的構造方式與構造time.strftime的時間格式串相同。對於日誌格式串除去上面提到的,還有一個: %(asctime)s。如果格式串中包含它的話,如果datefmt爲None,則%(asctime)s對應被替換爲time.strftime("%Y-%m-%d %H:%M:%S", time.localtime (record.created)) + “%03d” % record.msecs;否則,其被替換爲time.strftime(datefmt, time.localtime (record.created))。我這裏生成一個創建一個簡單的Formatter對象,以備後面使用。

expFormatter = Formatter ("%(levelname)s:%(name)s:%(message)s")

這個其實是後面提到的basicConfig( )所應用的一個Formatter。

Filter

這是一個Filter實現的示例性的類,其初始化函數爲:__init__(self, name='')。如果不傳入任何參數或者name = ‘.’,這個類的filter方法對任何LogRecord都返回True;如果傳入諸如“a.b”的name,則這個類的filter方會對任何name中包含‘a.b’的logger生成的LogRecord都返回true,其他的均返回Flase(這裏涉及到Logger的層次關係,後面細講)。

我們可以仿造(繼承也沒關係,不過我的感覺如果你的Filter和LogRecord名字無關,還是自己再實現一個把!)這個類實現你自己的Filter。構造自己的Filter類,必須要包含一個這樣的方法filter(self, record), 其中record爲一個LogRecord對象。返回值爲布爾類型,具體來說就是可以輸出該條日誌返回True,不可以輸出返回False。舉個很簡單的例子,實現一個只對id爲5的線程生成的LogRecord輸出True,其他的均爲False

class ThreadFilter(object):

    def filter(self, record):
        if  record.thread == 5:
            return True
        returen  False

 Filterer

這是一個對所有Filter管理的類,而且初始化得到的對象是不包含任何Filter的,具體的成員函數爲:

addFilter(self, filter):添加一個filter對象

removeFilter(self, filter):刪除一個filter對象

filter(self, record):用所有其管理的filter處理當前日誌record

從繼承圖總我們也可以看到它是Logger和Handler的基類,在這兩個類中,都自動調用了filter成員函數,具體來說,都在它們的handle(self, record)中進行了調用。所以只需用addFilter添加和removeFilter刪除filter就可以啦,具體使用可以不必關心。舉個例子(用上面提到的幾個Filter類以及幾個沒有講解的類,但相信看了繼承圖,應該是沒有理解難度的):

nameF = Filter(“a.b”)

threadF = ThreadFilter( )

root = RootLogger(WARNING)

hdlr = FileHandler('file1','a')

root.addFilter(threadF)

hdlr.addFilter(nameF)

Handler

這個類主要實現日誌的輸出,其本身不可以直接使用,必須子類化它並實現一定的函數纔可以處理日誌,諸如在logging模塊中主要有實現了三個類型的Handler,分別是StreamHandler, FileHandler以及NullHandler

(1)幾個可能用到的成員函數:

__init__(self, level=NOTSET):初始化一個Handler只需要指定它的日誌級別,默認爲NOTSET

setLevel(self, level):設置可以處理的最低日誌等級

setFormatter(self, fmt):設置使用的Formatter,結合前面提到的一些東西,比如, hdlr.setFormatter(expFormatter), 這樣一來,前面例子中得hdlr就使用expFormatter這個Formatter 。若果不做任何設置,其默認使用的Formatter爲 Formatter( ), 也即日誌格式串爲 “%(message)s”,日誌中對應顯示的時間爲time.strftime("%Y-%m-%d %H:%M:%S", time.localtime (record.created)) + “%03d” % record.msecs的樣子

(2)其子類中實現的成員函數:

emit(self, record):這個函數必須在子類中實現,否則就會生成NotImplementedError的異常

flush(self):持久或日誌,這個在Handler類中啥也沒有實現,在子類中要自己設計實現

close(self):這個可以根據不同的handler重寫,但重寫時一定要調用父類的這個函數

(3)成員變量:

      self.level :Handler的日誌級別,如果被處理的日誌的級別高於這個值,則處理;否則忽略

      name   :Handler的名字(這是用property加的一個變量)

StreamHandler 

把日誌輸出到一個流中得Handler,初始化函數爲__init__(self, stream=None),如果不指定任何輸出流,輸出到sys.stderr中

FileHandler

把日誌文件輸出到一個文件中得Handler,上面曾經給出過一個實際應用的例子

NullHandler

這個Handler什麼也不做,一般把他的一個對象添加到一個logger上以避免這樣的信息:No handlers could be found for logger XXX

Logger

這個類主要用來生成LogRecord,並轉交給它自身的handler處理。

(1)成員變量

        self.name :名字。由初始化函數給定

        self.level:logger的日誌級別,默認有初始化函數輸入,不給定的話爲NOTSET。如果被處理的日誌的級別高於這個值,則處理;否則忽略

        self.parent :這個logger的父logger,注意,這個類繼承沒有任何關係,下面的層次管理詳細道來!默認爲None 

       self.propagate :決定是否要讓它的父logger的handler來處理處理當前日誌。默認爲1,也即讓父logger來處理當前日誌

        self.handlers :當前包含的的logger,默認爲空

        self.disabled :決定是否可以處理日誌,默認爲0,也即要處理

        manager:這個變量在logger層次管理中加入的,指向這個logger的管理器(整個log系統原則上來說只有一個manager)

(2)一些常用成員函數

         setLevel(self, level):設定logger的日誌級別

         debug(self, msg, *args, **kwargs):生成一個debug級別的日誌並處理,例如logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)

         info(self, msg, *args, **kwargs):生成一個info級別的日誌並處理

         warning(self, msg, *args, **kwargs):生成一個warning級別的日誌並處理,warn(self, msg, *args, **kwargs)也實現相同功能

         error(self, msg, *args, **kwargs):生成一個error級別的日誌並處理

         exception(self, msg, *args):生成異常信息並處理,實際上它調用了error函數

         critical(self, msg, *args, **kwargs):生成critical級別的日誌並處理,fatal(self, msg, *args, **kwargs)也實現相同功能

         log(self, level, msg, *args, **kwargs):生成一個level級別的日誌並處理,例如logger.log(DEBUG, "We have a %s", "mysterious problem", exc_info=1)

        addHandler(self, hdlr):給當前logger添加一個handler hdlr

         removeHandler(self, hdlr):刪除掉當前logger中得一個handler hdlr

         getChild(self, suffix):獲取當前logger名的一個子孫logger. 例如,當前logger名爲“ab”,suffix爲“c”, 它將返回一個名爲“ab.c”的logger(這和logger的層次架構有關係)

RootLogger

這是爲搭建logger的層次架構而繼承Logger而構造的一個類,它繼承字Logger類,初始化函數爲__init__(self, level),其中level爲制定的logger級別。實例化這樣的一個對象就會生成一個名爲root的logger。事實上,模塊自己幫我們實例化一個名爲“root”,級別爲warning的RootLogger。一方面用它來充當層次架構中得根logger;一方面模塊級別的函數都使用它處理日誌(比如ogging.debug()就使用的它)。

2. 相互的調用關係

爲了方便查看源代碼,這裏給出一個可能得相互調用關係圖(以Logger.critical爲例):

調用關係

 

3. logging模塊級的函數和變量

(1)關於級別的定義,拷貝源碼過來:

CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

這是log系統預定義的日誌級別以及其對應的數值。通過getLevelName(level)對應的字符串名字,也可以通過addLevelName(level, levelName)添加你自己的級別以及級別名。

(2)直接應用以及logger管理,拷貝源碼了:

root = RootLogger(WARNING): 在模塊定義了根logger
Logger.root = root
Logger.manager = Manager(Logger.root)):定義了一個logger的管理器,從這裏也可以看到root是作爲一個根logger的

再看看其他的一些函數:

getLogger(name=None):這個函數最常用,要不向manager註冊一個名爲name的Logger,要不返回root

BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s",爲下面basicConfig定義的日誌格式串

basicConfig(**kwargs):這個函數主要針對root做配置。如果root已經有handler,它不做任何動作。其默認行爲(沒有參數)是爲root創建並添加一個使用BASIC_FORMAT格式串StreamHandler,其流指向sys.stderr. 可以給定的參數包括:

    filename  指定一個文件,去創建並添加一個 FileHandler 

    filemode  打開文件的方式,默認‘a‘    

    format    Handler要使用的日誌格式串

    datefmt  使用的時間格式串.
    level     設置root的日記級別

    stream    用這個流創建並添加一個StreamHandler。如果filename和它都指定,忽略它。

    debug(self, msg, *args, **kwargs):使用root生成一個debug級別的日誌並處理,例如loggin.debug("Houston, we have a %s", "thorny problem", exc_info=1)

    info(self, msg, *args, **kwargs):使用root生成一個info級別的日誌並處理

    warning(self, msg, *args, **kwargs):使用root生成一個warning級別的日誌並處理,warn(self, msg, *args, **kwargs)也實現相同功能

    error(self, msg, *args, **kwargs):使用root生成一個error級別的日誌並處理

    exception(self, msg, *args):使用root生成異常信息並處理,實際上它調用了error函數

    critical(self, msg, *args, **kwargs):使用root生成critical級別的日誌並處理,fatal(self, msg, *args, **kwargs)也實現相同功能

     log(self, level, msg, *args, **kwargs):使用root生成一個level級別的日誌並處理,例如loggingr.log(DEBUG, "We have a %s", "mysterious problem", exc_info=1)

    disable(level):通過manager通知所有的logger,低於level的所有日誌都不輸出

4. logger的層次架構

通過Manager類,loggin模塊實現了一個層次化管理的logger管理系統。比如,運行下面代碼:

import logging
logging.getLogger("a.b.c")
logging.getLogger("a.b.d")
logging.getLogger("a.b")

很自然,會生成三個Logger,名字分別爲“a.b.c”, “a.b.d” 和“a.b”,但同時還實現了一種樹一樣的層級關係。root 會有一個字Logger “a.b”, 而Logger “a.b”則會有兩個子Logger “a.b.c”和“a.b.d”。這樣做的好處就是:

1)可以有選擇的輸出模塊中子模塊。以實現soap客戶端的suds爲例,在實際應用中可以很簡單的使用如下語句指定輸出 suds.client debug級別以上的日誌,而其他模塊,比如suds.transport 則不會輸出任何日誌信息。

import logging
logging.basicConfig( )
logging.getLogger('suds.client').setLevel(logging.DEBUG)

2)方便配置Handler。依賴層級之間的回調關係,可以不必爲每個Logger指定Handler,只要其父Logger有指定的Handler就可以。最簡單的做法是指指定根 Logger的Handler就可以,上例中使用的就是這種辦法

5. Logger的配置及使用

基本的配置可以學學basicConfig函數,拷貝源碼了

    _acquireLock()
    try:
        if len(root.handlers) == 0:
            filename = kwargs.get("filename")
            if filename:
                mode = kwargs.get("filemode", 'a')
                hdlr = FileHandler(filename, mode)
            else:
                stream = kwargs.get("stream")
                hdlr = StreamHandler(stream)
            fs = kwargs.get("format", BASIC_FORMAT)
            dfs = kwargs.get("datefmt", None)
            fmt = Formatter(fs, dfs)
            hdlr.setFormatter(fmt)
            root.addHandler(hdlr)
            level = kwargs.get("level")
            if level is not None:
                root.setLevel(level)
    finally:
        _releaseLock()

還有我從別的被人拷貝的[4]

import logging

#create logger
logger = logging.getLogger("simple_example")
logger.setLevel(logging.DEBUG)

#create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

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

#add formatter to ch
ch.setFormatter(formatter)

#add ch to logger
logger.addHandler(ch)

#"application" code
logger.debug("debug message")
logger.info("info message")
logger.warn("warn message")
logger.error("error message")
logger.critical("critical message")

另外, logging.config模塊可以支持從配置讀入,相同的實現第二個例子【4】:

配置文件:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

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

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

程序如下:

import logging
import logging.config

logging.config.fileConfig("logging.conf") 

#create logger
logger = logging.getLogger("simpleExample")

#"application" code
logger.debug("debug message")
logger.info("info message")
logger.warn("warn message")
logger.error("error message")
logger.critical("critical message")

6. logging.handlers模塊提供的其他幾個類型的handler【2】

BaseRotatingHandler是所有輪徇日誌的基類,不能直接使用。但是可以使用RotatingFileHandler和TimeRotatingFileHandler。RotatingFileHandler實例發送信息到磁盤文件,並且限制最大的日誌文件大小,並適時輪徇。 TimeRotatingFileHandler實例發送錯誤信息到磁盤,並在適當的事件間隔進行輪徇。 SocketHandler實例發送日誌到TCP/IP socket。 DatagramHandler實例發送錯誤信息通過UDP協議。 SMTPHandler實例發送錯誤信息到特定的email地址。 SysLogHandler實例發送日誌到UNIX syslog服務,並支持遠程syslog服務。 NTEventLogHandler實例發送日誌到WindowsNT/2000/XP事件日誌。 MemoryHandler實例發送日誌到內存中的緩衝區,並在達到特定條件時清空。 HTTPHandler實例發送錯誤信息到HTTP服務器,通過GET或POST方法。

 

 參考文獻:

【1】http://www.python.org/dev/peps/pep-0282/

【2】http://www.cnblogs.com/sislcb/archive/2008/11/25/1340627.html

【3】http://www.cnblogs.com/SophiaTang/archive/2011/09/19/2181471.html

【4】http://crazier9527.iteye.com/blog/290026

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