(一)、日志相關概念
1、日志的作用
通過log的分析,可以方便用戶了解系統或軟件、應用的運行情況;如果你的應用log足夠豐富,也可以分析以往用戶的操作行為、類型喜好、地域分布或其他更多信息;如果一個應用的log同時也分了多個級別,那么可以很輕易地分析得到該應用的健康狀況,及時發現問題并快速定位、解決問題,補救損失。
簡單來講就是,我們通過記錄和分析日志可以了解一個系統或軟件程序運行情況是否正常,也可以在應用程序出現故障時快速定位問題。比如,做運維的同學,在接收到報警或各種問題反饋后,進行問題排查時通常都會先去看各種日志,大部分問題都可以在日志中找到答案。再比如,做開發的同學,可以通過IDE控制臺上輸出的各種日志進行程序調試。對于運維老司機或者有經驗的開發人員,可以快速的通過日志定位到問題的根源。可見,日志的重要性不可小覷。日志的作用可以簡單總結為以下3點:
程序調試
了解軟件程序運行情況,是否正常
軟件程序運行故障分析與問題定位
如果應用的日志信息足夠詳細和豐富,還可以用來做用戶行為分析,如:分析用戶的操作行為、類型洗好、地域分布以及其它更多的信息,由此可以實現改進業務、提高商業利益。
2、日志的等級
我們先來思考下下面的兩個問題:
作為開發人員,在開發一個應用程序時需要什么日志信息?在應用程序正式上線后需要什么日志信息?
作為應用運維人員,在部署開發環境時需要什么日志信息?在部署生產環境時需要什么日志信息?
在軟件開發階段或部署開發環境時,為了盡可能詳細的查看應用程序的運行狀態來保證上線后的穩定性,我們可能需要把該應用程序所有的運行日志全部記錄下來進行分析,這是非常耗費機器性能的。當應用程序正式發布或在生產環境部署應用程序時,我們通常只需要記錄應用程序的異常信息、錯誤信息等,這樣既可以減小服務器的I/O壓力,也可以避免我們在排查故障時被淹沒在日志的海洋里。那么,怎樣才能在不改動應用程序代碼的情況下實現在不同的環境記錄不同詳細程度的日志呢?這就是日志等級的作用了,我們通過配置文件指定我們需要的日志等級就可以了。
不同的應用程序所定義的日志等級可能會有所差別,分的詳細點的會包含以下幾個等級:
級別 | 何時使用 |
---|---|
DEBUG | 詳細信息,典型地調試問題時會感興趣。 詳細的debug信息。 |
INFO | 證明事情按預期工作。 關鍵事件 |
WARNING | 表明發生了一些意外,或者不久的將來會發生問題(如‘磁盤滿了’)。軟件還是在正常工作。 |
ERROR | 由于更嚴重的問題,軟件已不能執行一些功能了。 一般錯誤消息。 |
CRITICAL | 嚴重錯誤,表明軟件已不能繼續運行了。 |
NOTICE | 不是錯誤,但是可能需要處理。普通但是重要的事件。 |
ALERT | 需要立即修復,例如系統數據庫損壞。 |
EMERGENCY | 緊急情況,系統不可用(例如系統崩潰),一般會通知所有用戶。 |
3、日志字段信息與日志格式
一條日志信息對應的是一個事件的發生,而一個事件通常需要包括以下幾個內容:
事件發生時間
事件發生位置
事件的嚴重程度--日志級別
事件內容
上面這些都是一條日志記錄中可能包含的字段信息,當然還可以包括一些其他信息,如進程ID、進程名稱、線程ID、線程名稱等。日志格式就是用來定義一條日志記錄中包含那些字段的,且日志格式通常都是可以自定義的。
4、日志功能的實現
幾乎所有開發語言都會內置日志相關功能,或者會有比較優秀的第三方庫來提供日志操作功能,比如:log4j,log4php等。它們功能強大、使用簡單。Python自身也提供了一個用于記錄日志的標準庫模塊--logging。
(二)logging模塊
logging模塊是Python內置的標準模塊,主要用于輸出運行日志,可以設置輸出日志的等級、日志保存路徑、日志文件回滾等;相比print,具備如下優點:
可以通過設置不同的日志等級,在release版本中只輸出重要信息,而不必顯示大量的調試信息;
print將所有信息都輸出到標準輸出中,嚴重影響開發者從標準輸出中查看其它數據;logging則可以由開發者決定將信息輸出到什么地方,以及怎么輸出。
1、 logging模塊的日志級別
logging模塊默認定義了以下幾個日志等級,它允許開發人員自定義其他日志級別,但是這是不被推薦的,尤其是在開發供別人使用的庫時,因為這會導致日志級別的混亂。
日志等級Level | 描述 |
---|---|
DEBUG | 最詳細的日志信息,典型應用場景是 問題診斷 |
INFO | 信息詳細程度僅次于DEBUG,通常只記錄關鍵節點信息,用于確認一切都是按照我們預期的那樣進行工作 |
WARNING | 當某些不期望的事情發生時記錄的信息(如,磁盤可用空間較低),但是此時應用程序還是正常運行的 |
ERROR | 由于一個更嚴重的問題導致某些功能不能正常運行時記錄的信息 |
CRITICAL | 當發生嚴重錯誤,導致應用程序不能繼續運行時記錄的信息 |
開發應用程序或部署開發環境時,可以使用DEBUG或INFO級別的日志獲取盡可能詳細的日志信息來進行開發或部署調試;
應用上線或部署生產環境時,應該使用WARNING或ERROR或CRITICAL級別的日志來降低機器的I/O壓力和提高獲取錯誤日志信息的效率。日志級別的指定通常都是在應用程序的配置文件中進行指定的。
2、logging模塊的使用方式介紹
logging模塊提供了兩種記錄日志的方式:
第一種方式是使用logging提供的模塊級別的函數
第二種方式是使用Logging日志系統的四大組件
其實,logging所提供的模塊級別的日志記錄函數也是對logging日志系統相關類的封裝而已。
logging模塊定義的模塊級別的常用函數
函數 | 說明 |
---|---|
logging.debug(msg, *args, **kwargs) | 創建一條嚴重級別為DEBUG的日志記錄 |
logging.info(msg, *args, **kwargs) | 創建一條嚴重級別為INFO的日志記錄 |
logging.warning(msg, *args, **kwargs) | 創建一條嚴重級別為WARNING的日志記錄 |
logging.error(msg, *args, **kwargs) | 創建一條嚴重級別為ERROR的日志記錄 |
logging.critical(msg, *args, **kwargs) | 創建一條嚴重級別為CRITICAL的日志記錄 |
logging.log(level, *args, **kwargs) | 創建一條嚴重級別為level的日志記錄 |
logging.basicConfig(**kwargs) | 對root logger進行一次性配置 |
其中logging.basicConfig(**kwargs)函數用于指定“要記錄的日志級別”、“日志格式”、“日志輸出位置”、“日志文件的打開模式”等信息,其他幾個都是用于記錄各個級別日志的函數。
3、第一種使用方式:簡單配置
import logging
logging.debug("debug_msg")
logging.info("info_msg")
logging.warning("warning_msg")
logging.error("error_msg")
logging.critical("critical_msg")
輸出結果
WARNING:root:warning_msg
ERROR:root:error_msg
CRITICAL:root:critical_msg
默認情況下Python的logging模塊將日志打印到了標準輸出中,且只顯示了大于等于WARNING級別的日志,這說明默認的日志級別設置為WARNING(日志級別等級CRITICAL > ERROR > WARNING > INFO > DEBUG)
默認輸出格式為
默認的日志格式為日志級別:Logger名稱:用戶輸出消息
這里可以用 logging.basicConfig()函數調整日志級別、輸出格式等
簡單的例子
import logging
logging.basicConfig(level=logging.DEBUG,
format="%(asctime)s %(name)s %(levelname)s %(message)s",
datefmt = '%Y-%m-%d %H:%M:%S %a' #注意月份和天數不要搞亂了,這里的格式化符與time模塊相同
)
logging.debug("msg1")
logging.info("msg2")
logging.warning("msg3")
logging.error("msg4")
logging.critical("msg5")
輸出結果
2018-05-09 23:37:49 Wed root DEBUG msg1
2018-05-09 23:37:49 Wed root INFO msg2
2018-05-09 23:37:49 Wed root WARNING msg3
2018-05-09 23:37:49 Wed root ERROR msg4
2018-05-09 23:37:49 Wed root CRITICAL msg5
logging.basicConfig()函數包含參數說明
參數名稱 | 描述 |
---|---|
filename | 指定日志輸出目標文件的文件名(可以寫文件名也可以寫文件的完整的絕對路徑,寫文件名日志放執行文件目錄下,寫完整路徑按照完整路徑生成日志文件),指定該設置項后日志信心就不會被輸出到控制臺了 |
filemode | 指定日志文件的打開模式,默認為'a'。需要注意的是,該選項要在filename指定時才有效 |
format | 指定日志格式字符串,即指定日志輸出時所包含的字段信息以及它們的順序。logging模塊定義的格式字段下面會列出。 |
datefmt | 指定日期/時間格式。需要注意的是,該選項要在format中包含時間字段%(asctime)s時才有效 |
level | 指定日志器的日志級別 |
stream | 指定日志輸出目標stream,如sys.stdout、sys.stderr以及網絡stream。需要說明的是,stream和filename不能同時提供,否則會引發 ValueError 異常 |
style | Python 3.2中新添加的配置項。指定format格式字符串的風格,可取值為'%'、'{'和'$',默認為'%' |
handlers | Python 3.3中新添加的配置項。該選項如果被指定,它應該是一個創建了多個Handler的可迭代對象,這些handler將會被添加到root logger。需要說明的是:filename、stream和handlers這三個配置項只能有一個存在,不能同時出現2個或3個,否則會引發ValueError異常。 |
logging模塊中定義好的可以用于format格式字符串說明
字段/屬性名稱 | 使用格式 | 描述 |
---|---|---|
asctime | %(asctime)s | 將日志的時間構造成可讀的形式,默認情況下是‘2016-02-08 12:00:00,123’精確到毫秒 |
name | %(name)s | 所使用的日志器名稱,默認是'root',因為默認使用的是 rootLogger |
filename | %(filename)s | 調用日志輸出函數的模塊的文件名; pathname的文件名部分,包含文件后綴 |
funcName | %(funcName)s | 由哪個function發出的log, 調用日志輸出函數的函數名 |
levelname | %(levelname)s | 日志的最終等級(被filter修改后的) |
message | %(message)s | 日志信息, 日志記錄的文本內容 |
lineno | %(lineno)d | 當前日志的行號, 調用日志輸出函數的語句所在的代碼行 |
levelno | %(levelno)s | 該日志記錄的數字形式的日志級別(10, 20, 30, 40, 50) |
pathname | %(pathname)s | 完整路徑 ,調用日志輸出函數的模塊的完整路徑名,可能沒有 |
process | %(process)s | 當前進程, 進程ID。可能沒有 |
processName | %(processName)s | 進程名稱,Python 3.1新增 |
thread | %(thread)s | 當前線程, 線程ID。可能沒有 |
threadName | %(thread)s | 線程名稱 |
module | %(module)s | 調用日志輸出函數的模塊名, filename的名稱部分,不包含后綴即不包含文件后綴的文件名 |
created | %(created)f | 當前時間,用UNIX標準的表示時間的浮點數表示; 日志事件發生的時間--時間戳,就是當時調用time.time()函數返回的值 |
relativeCreated | %(relativeCreated)d | 輸出日志信息時的,自Logger創建以 來的毫秒數; 日志事件發生的時間相對于logging模塊加載時間的相對毫秒數 |
msecs | %(msecs)d | 日志事件發生事件的毫秒部分。logging.basicConfig()中用了參數datefmt,將會去掉asctime中產生的毫秒部分,可以用這個加上 |
升級版日志例子
import logging
LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置輸出日志格式
DATE_FORMAT = '%Y-%m-%d %H:%M:%S %a ' #配置輸出時間的格式,注意月份和天數不要搞亂了
logging.basicConfig(level=logging.DEBUG,
format=LOG_FORMAT,
datefmt = DATE_FORMAT ,
filename=r"d:\test\test.log" #有了filename參數就不會直接輸出顯示到控制臺,而是直接寫入文件
)
logging.debug("msg1")
logging.info("msg2")
logging.warning("msg3")
logging.error("msg4")
logging.critical("msg5")
說明
logging.basicConfig()函數是一個一次性的簡單配置工具使,也就是說只有在第一次調用該函數時會起作用,后續再次調用該函數時完全不會產生任何操作的,多次調用的設置并不是累加操作。
日志器(Logger)是有層級關系的,上面調用的logging模塊級別的函數所使用的日志器是RootLogger類的實例,其名稱為'root',它是處于日志器層級關系最頂層的日志器,且該實例是以單例模式存在的。
如果要記錄的日志中包含變量數據,可使用一個格式字符串作為這個事件的描述消息(logging.debug、logging.info等函數的第一個參數),然后將變量數據作為第二個參數*args的值進行傳遞,
logging.warning('%s is %d years old.', 'Tom', 10),
輸出內容為
WARNING:root:Tom is 10 years old.
logging.debug(), logging.info()等方法的定義中,除了msg和args參數外,還有一個**kwargs參數。它們支持3個關鍵字參數: exc_info, stack_info, extra,下面對這幾個關鍵字參數作個說明。關于exc_info, stack_info, extra關鍵詞參數的說明:見參考資料1。(了解)
4、第二種使用方式:日志流處理流程
上面簡單配置的方法例子中我們了解到了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日志模塊四大組件
在介紹logging模塊的日志流處理流程之前,我們先來介紹下logging模塊的四大組件:
組件名稱 | 對應類名 | 功能描述 |
---|---|---|
日志器 | Logger | 提供了應用程序可一直使用的接口 |
處理器 | Handler | 將logger創建的日志記錄發送到合適的目的輸出 |
過濾器 | Filter | 提供了更細粒度的控制工具來決定輸出哪條日志記錄,丟棄哪條日志記錄 |
格式器 | Formatter | 決定日志記錄的最終輸出格式 |
logging模塊就是通過這些組件來完成日志處理的,上面所使用的logging模塊級別的函數也是通過這些組件對應的類來實現的。
這些組件之間的關系描述:
- 日志器(logger)需要通過處理器(handler)將日志信息輸出到目標位置,如:文件、sys.stdout、網絡等;
- 不同的處理器(handler)可以將日志輸出到不同的位置;
- 日志器(logger)可以設置多個處理器(handler)將同一條日志記錄輸出到不同的位置;
- 每個處理器(handler)都可以設置自己的過濾器(filter)實現日志過濾,從而只保留感興趣的日志;
- 每個處理器(handler)都可以設置自己的格式器(formatter)實現同一條日志以不同的格式輸出到不同的地方。
簡單點說就是:日志器(logger)是入口,真正干活兒的是處理器(handler),處理器(handler)還可以通過過濾器(filter)和格式器(formatter)對要輸出的日志內容做過濾和格式化等處理操作。
logging日志模塊相關類及其常用方法介紹
與logging四大組件相關的類:Logger, Handler, Filter, Formatter。
Logger類
Logger對象有3個任務要做:
- 1)向應用程序代碼暴露幾個方法,使應用程序可以在運行時記錄日志消息;
- 2)基于日志嚴重等級(默認的過濾設施)或filter對象來決定要對哪些日志進行后續處理;
- 3)將日志消息傳送給所有感興趣的日志handlers。
Logger對象最常用的方法分為兩類:配置方法 和 消息發送方法
最常用的配置方法如下:
方法 | 描述 |
---|---|
Logger.setLevel() | 設置日志器將會處理的日志消息的最低嚴重級別 |
Logger.addHandler() 和 Logger.removeHandler() | 為該logger對象添加 和 移除一個handler對象 |
Logger.addFilter() 和 Logger.removeFilter() | 為該logger對象添加 和 移除一個filter對象 |
logger對象配置完成后,可以使用下面的方法來創建日志記錄:
方法 | 描述 |
---|---|
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() | 創建一個與它們的方法名對應等級的日志記錄 |
Logger.exception() | 創建一個類似于Logger.error()的日志消息 |
Logger.log() | 需要獲取一個明確的日志level參數來創建一個日志記錄 |
一個Logger對象呢?一種方式是通過Logger類的實例化方法創建一個Logger類的實例,但是我們通常都是用第二種方式--logging.getLogger()方法。
logging.getLogger()方法有一個可選參數name,該參數表示將要返回的日志器的名稱標識,如果不提供該參數,則其值為'root'。若以相同的name參數值多次調用getLogger()方法,將會返回指向同一個logger對象的引用。
多次使用注意不能創建多個logger,否則會出現重復輸出日志現象。
關于logger的層級結構與有效等級的說明:
- logger的名稱是一個以'.'分割的層級結構,每個'.'后面的logger都是'.'前面的logger的children,例如,有一個名稱為 foo 的logger,其它名稱分別為 foo.bar, foo.bar.baz 和 foo.bam都是 foo 的后代。
- logger有一個"有效等級(effective level)"的概念。如果一個logger上沒有被明確設置一個level,那么該logger就是使用它parent的level;如果它的parent也沒有明確設置level則繼續向上查找parent的parent的有效level,依次類推,直到找到個一個明確設置了level的祖先為止。需要說明的是,root logger總是會有一個明確的level設置(默認為 WARNING)。當決定是否去處理一個已發生的事件時,logger的有效等級將會被用來決定是否將該事件傳遞給該logger的handlers進行處理。
- child loggers在完成對日志消息的處理后,默認會將日志消息傳遞給與它們的祖先loggers相關的handlers。因此,我們不必為一個應用程序中所使用的所有loggers定義和配置handlers,只需要為一個頂層的logger配置handlers,然后按照需要創建child loggers就可足夠了。我們也可以通過將一個logger的propagate屬性設置為False來關閉這種傳遞機制。
Handler類
Handler對象的作用是(基于日志消息的level)將消息分發到handler指定的位置(文件、網絡、郵件等)。Logger對象可以通過addHandler()方法為自己添加0個或者更多個handler對象。比如,一個應用程序可能想要實現以下幾個日志需求:
- 1)把所有日志都發送到一個日志文件中;
- 2)把所有嚴重級別大于等于error的日志發送到stdout(標準輸出);
- 3)把所有嚴重級別為critical的日志發送到一個email郵件地址。這種場景就需要3個不同的handlers,每個handler復雜發送一個特定嚴重級別的日志到一個特定的位置。
Handler.setLevel(lel):指定被處理的信息級別,低于lel級別的信息將被忽略
Handler.setFormatter():給這個handler選擇一個格式
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或刪除一個filter對象
需要說明的是,應用程序代碼不應該直接實例化和使用Handler實例。因為Handler是一個基類,它只定義了素有handlers都應該有的接口,同時提供了一些子類可以直接使用或覆蓋的默認行為。下面是一些常用的Handler:
Handler | 描述 |
---|---|
logging.StreamHandler | 將日志消息發送到輸出到Stream,如std.out, std.err或任何file-like對象。 |
logging.FileHandler | 將日志消息發送到磁盤文件,默認情況下文件大小會無限增長 |
logging.handlers.RotatingFileHandler | 將日志消息發送到磁盤文件,并支持日志文件按大小切割 |
logging.hanlders.TimedRotatingFileHandler | 將日志消息發送到磁盤文件,并支持日志文件按時間切割 |
logging.handlers.HTTPHandler | 將日志消息以GET或POST的方式發送給一個HTTP服務器 |
logging.handlers.SMTPHandler | 將日志消息發送給一個指定的email地址 |
logging.NullHandler | 該Handler實例會忽略error messages,通常被想使用logging的library開發者使用來避免'No handlers could be found for logger XXX'信息的出現。 |
Formater類
Formater對象用于配置日志信息的最終順序、結構和內容。與logging.Handler基類不同的是,應用代碼可以直接實例化Formatter類。另外,如果你的應用程序需要一些特殊的處理行為,也可以實現一個Formatter的子類來完成。
Formatter類的構造方法定義如下:
logging.Formatter.__init__(fmt``=``None``, datefmt``=``None``, style``=``'%'``)
可見,該構造方法接收3個可選參數:
- fmt:指定消息格式化字符串,如果不指定該參數則默認使用message的原始值
- datefmt:指定日期格式字符串,如果不指定該參數則默認使用"%Y-%m-%d %H:%M:%S"
- style:Python 3.2新增的參數,可取值為 '%', '{'和 '$',如果不指定該參數則默認使用'%'
一般直接用logging.Formatter(fmt, datefmt)
Filter類(暫時了解)
Filter可以被Handler和Logger用來做比level更細粒度的、更復雜的過濾功能。Filter是一個過濾器基類,它只允許某個logger層級下的日志事件通過過濾。該類定義如下:
class logging.Filter(name='')
filter(record)
比如,一個filter實例化時傳遞的name參數值為'A.B',那么該filter實例將只允許名稱為類似如下規則的loggers產生的日志記錄通過過濾:'A.B','A.B,C','A.B.C.D','A.B.D',而名稱為'A.BB', 'B.A.B'的loggers產生的日志則會被過濾掉。如果name的值為空字符串,則允許所有的日志事件通過過濾。
filter方法用于具體控制傳遞的record記錄是否能通過過濾,如果該方法返回值為0表示不能通過過濾,返回值為非0表示可以通過過濾。
說明:
- 如果有需要,也可以在filter(record)方法內部改變該record,比如添加、刪除或修改一些屬性。
- 我們還可以通過filter做一些統計工作,比如可以計算下被一個特殊的logger或handler所處理的record數量等。
日志流處理簡要流程
1、創建一個logger
2、設置下logger的日志的等級
3、創建合適的Handler(FileHandler要有路徑)
4、設置下每個Handler的日志等級
5、創建下日志的格式
6、向Handler中添加上面創建的格式
7、將上面創建的Handler添加到logger中
8、打印輸出logger.debug\logger.info\logger.warning\logger.error\logger.critical
例子
import logging
?
#創建logger,如果參數為空則返回root logger
logger = logging.getLogger("nick")
logger.setLevel(logging.DEBUG) #設置logger日志等級
?
#創建handler
fh = logging.FileHandler("test.log",encoding="utf-8")
ch = logging.StreamHandler()
?
#設置輸出日志格式
formatter = logging.Formatter(
fmt="%(asctime)s %(name)s %(filename)s %(message)s",
datefmt="%Y/%m/%d %X"
)
?
#注意 logging.Formatter的大小寫
#為handler指定輸出格式,注意大小寫
fh.setFormatter(formatter)
ch.setFormatter(formatter)
?
#為logger添加的日志處理器
logger.addHandler(fh)
logger.addHandler(ch)
?
#輸出不同級別的log
logger.warning("泰拳警告")
logger.info("提示")
logger.error("錯誤")
python logging 重復寫日志問題
用Python的logging模塊記錄日志時,可能會遇到重復記錄日志的問題,第一條記錄寫一次,第二條記錄寫兩次,第三條記錄寫三次
原因:沒有移除handler 解決:在日志記錄完之后removeHandler
例子
def log(msg):
#創建logger,如果參數為空則返回root logger
logger = logging.getLogger("nick")
logger.setLevel(logging.DEBUG) #設置logger日志等級
?
#創建handler
fh = logging.FileHandler("test.log",encoding="utf-8")
ch = logging.StreamHandler()
?
#設置輸出日志格式
formatter = logging.Formatter(
fmt="%(asctime)s %(name)s %(filename)s %(message)s",
datefmt="%Y/%m/%d %X"
)
?
#為handler指定輸出格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)
?
#為logger添加的日志處理器
logger.addHandler(fh)
logger.addHandler(ch)
?
# 輸出不同級別的log
logger.info(msg)
?
log("泰拳警告")
log("提示")
log("錯誤")
輸出結果
2018/05/10 20:06:18 nick test.py 泰拳警告
2018/05/10 20:06:18 nick test.py 提示
2018/05/10 20:06:18 nick test.py 提示
2018/05/10 20:06:18 nick test.py 錯誤
2018/05/10 20:06:18 nick test.py 錯誤
2018/05/10 20:06:18 nick test.py 錯誤
分析:可以看到輸出結果有重復打印
原因:第二次調用log的時候,根據getLogger(name)里的name獲取同一個logger,而這個logger里已經有了第一次你添加的handler,第二次調用又添加了一個handler,所以,這個logger里有了兩個同樣的handler,以此類推,調用幾次就會有幾個handler。
解決方案1
添加removeHandler語句
import logging
def log(msg):
#創建logger,如果參數為空則返回root logger
logger = logging.getLogger("nick")
logger.setLevel(logging.DEBUG) #設置logger日志等級
?
#創建handler
fh = logging.FileHandler("test.log",encoding="utf-8")
ch = logging.StreamHandler()
?
#設置輸出日志格式
formatter = logging.Formatter(
fmt="%(asctime)s %(name)s %(filename)s %(message)s",
datefmt="%Y/%m/%d %X"
)
?
#為handler指定輸出格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)
?
#為logger添加的日志處理器
logger.addHandler(fh)
logger.addHandler(ch)
?
# 輸出不同級別的log
logger.info(msg)
?
#解決方案1,添加removeHandler語句,每次用完之后移除Handler
logger.removeHandler(fh)
logger.removeHandler(ch)
?
?
log("泰拳警告")
log("提示")
log("錯誤")
解決方案2
在log方法里做判斷,如果這個logger已有handler,則不再添加handler。
import logging
def log(msg):
#創建logger,如果參數為空則返回root logger
logger = logging.getLogger("nick")
logger.setLevel(logging.DEBUG) #設置logger日志等級
?
#解決方案2:這里進行判斷,如果logger.handlers列表為空,則添加,否則,直接去寫日志
if not logger.handlers:
#創建handler
fh = logging.FileHandler("test.log",encoding="utf-8")
ch = logging.StreamHandler()
?
#設置輸出日志格式
formatter = logging.Formatter(
fmt="%(asctime)s %(name)s %(filename)s %(message)s",
datefmt="%Y/%m/%d %X"
)
?
#為handler指定輸出格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)
?
#為logger添加的日志處理器
logger.addHandler(fh)
logger.addHandler(ch)
?
# 輸出不同級別的log
logger.info(msg)
?
?
log("泰拳警告")
log("提示")
log("錯誤")
logger調用方式例子
import logging
def log():
#創建logger,如果參數為空則返回root logger
logger = logging.getLogger("nick")
logger.setLevel(logging.DEBUG) #設置logger日志等級
?
#這里進行判斷,如果logger.handlers列表為空,則添加,否則,直接去寫日志
if not logger.handlers:
#創建handler
fh = logging.FileHandler("test.log",encoding="utf-8")
ch = logging.StreamHandler()
?
#設置輸出日志格式
formatter = logging.Formatter(
fmt="%(asctime)s %(name)s %(filename)s %(message)s",
datefmt="%Y/%m/%d %X"
)
?
#為handler指定輸出格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)
?
#為logger添加的日志處理器
logger.addHandler(fh)
logger.addHandler(ch)
?
return logger #直接返回logger
?
logger = log()
logger.warning("泰拳警告")
logger.info("提示")
logger.error("錯誤")
logger.debug("查錯")