百度百科的日誌概述:html
Windows網絡操做系統都設計有各類各樣的日誌文件,如應用程序日誌,安全日誌、系統日誌、Scheduler服務日誌、FTP日誌、WWW日誌、DNS服務器日誌等等,這些根據你的系統開啓的服務的不一樣而有所不一樣。咱們在系統上進行一些操做時,這些日誌文件一般會記錄下咱們操做的一些相關內容,這些內容對系統安全工做人員至關有用。好比說有人對系統進行了IPC探測,系統就會在安全日誌裏迅速地記下探測者探測時所用的IP、時間、用戶名等,用FTP探測後,就會在FTP日誌中記下IP、時間、探測所用的用戶名等。安全
我映像中的日誌:bash
查看日誌是開發人員平常獲取信息、排查異常、發現問題的最好途徑,日誌記錄中一般會標記有異常產生的緣由、發生時間、具體錯誤行數等信息,這極大的節省了咱們的排查時間,無形中提升了編碼效率。服務器
咱們能夠按照輸出終端進行分類,也能夠按照日誌級別進行分類。輸出終端指的是將日誌在控制檯輸出顯示和將日誌存入文件;日誌級別指的是 Debug、Info、WARNING、ERROR以及CRITICAL等嚴重等級進行劃分。微信
logging提供了一組便利的日誌函數,它們分別是:debug()、 info()、 warning()、 error() 和 critical()。logging函數根據它們用來跟蹤的事件的級別或嚴重程度來命名。標準級別及其適用性描述以下(以嚴重程度遞增排序):網絡
每一個級別對應的數字值爲 CRITICAL:50,ERROR:40,WARNING:30,INFO:20,DEBUG:10,NOTSET:0。 Python 中日誌的默認等級是 WARNING,DEBUG 和 INFO 級別的日誌將不會獲得顯示,在 logging 中更改設置。編輯器
使用 logging 在控制檯打印日誌,這裏咱們用 Pycharm 編輯器來觀察:函數
import logging
logging.debug('崔慶才丨靜覓、韋世東丨奎因')
logging.warning('邀請你關注微信公衆號【進擊的 Coder】')
logging.info('和大佬一塊兒coding、共同進步')
複製代碼
從上圖運行的結果來看,的確只顯示了 WARNING 級別的信息,驗證了上面的觀點。同時也在控制檯輸出了日誌內容,默認狀況下 Python 中使用 logging 模塊中的函數打印日誌,日誌只會在控制檯輸出,而不會保存到日文件。
有什麼辦法能夠改變默認的日誌級別呢?學習
固然是有的,logging 中提供了 basicConfig 讓使用者能夠適時調節默認日誌級別,咱們能夠將上面的代碼改成:ui
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug('崔慶才丨靜覓、韋世東丨奎因')
logging.warning('邀請你關注微信公衆號【進擊的 Coder】')
logging.info('和大佬一塊兒coding、共同進步')
複製代碼
在 basicConfig 中設定 level 參數的級別便可。
思考:若是設定級別爲 logging.INFO,那 DEBUG 信息可以顯示麼?
剛纔演示瞭如何在控制檯輸出日誌內容,而且自由設定日誌的級別,那如今就來看看如何將日誌保存到文件。依舊是強大的 basicConfig,咱們再將上面的代碼改成:
import logging
logging.basicConfig(level=logging.DEBUG, filename='coder.log', filemode='a')
logging.debug('崔慶才丨靜覓、韋世東丨奎因')
logging.warning('邀請你關注微信公衆號【進擊的 Coder】')
logging.info('和大佬一塊兒coding、共同進步')
複製代碼
在配置中填寫 filename (指定文件名) 和 filemode (文件寫入方式),控制檯的日誌輸出就不見了,那麼 coder.log 會生成麼?
在 .py 文件的同級目錄生成了名爲 coder.log 的日誌。
經過簡單的代碼設置,咱們就完成了日誌文件在控制檯和文件中的輸出。那既在控制檯顯示又能保存到文件中呢?
logging所提供的模塊級別的日誌記錄函數是對logging日誌系統相關類的封裝
logging 模塊提供了兩種記錄日誌的方式:
這裏提到的級別函數就是上面所用的 DEBGE、ERROR 等級別,而四大組件則是指 loggers、handlers、filters 和 formatters 這幾個組件,下圖簡單明瞭的闡述了它們各自的做用:
日誌器(logger)是入口,真正工做的是處理器(handler),處理器(handler)還能夠經過過濾器(filter)和格式器(formatter)對要輸出的日誌內容作過濾和格式化等處理操做。下面介紹下與logging四大組件相關的類:Logger, Handler, Filter, Formatter。
Logger類
Logger 對象有3個工做要作:
1)嚮應用程序代碼暴露幾個方法,使應用程序能夠在運行時記錄日誌消息;
2)基於日誌嚴重等級(默認的過濾設施)或filter對象來決定要對哪些日誌進行後續處理;
3)將日誌消息傳送給全部感興趣的日誌handlers。
複製代碼
Logger對象最經常使用的方法分爲兩類:配置方法 和 消息發送方法
最經常使用的配置方法以下:
關於Logger.setLevel()方法的說明:
內建等級中,級別最低的是DEBUG,級別最高的是CRITICAL。例如setLevel(logging.INFO),此時函數參數爲INFO,那麼該logger將只會處理INFO、WARNING、ERROR和CRITICAL級別的日誌,而DEBUG級別的消息將會被忽略/丟棄。
logger對象配置完成後,可使用下面的方法來建立日誌記錄:
那麼,怎樣獲得一個Logger對象呢?一種方式是經過Logger類的實例化方法建立一個Logger類的實例,可是咱們一般都是用第二種方式--logging.getLogger()方法。logging.getLogger()方法有一個可選參數name,該參數表示將要返回的日誌器的名稱標識,若是不提供該參數,則其值爲'root'。若以相同的name參數值屢次調用getLogger()方法,將會返回指向同一個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中只有很是少數的方法是須要應用開發人員去關心的。對於使用內建handler對象的應用開發人員來講,彷佛惟一相關的handler方法就是下面這幾個配置方法:
須要說明的是,應用程序代碼不該該直接實例化和使用Handler實例。由於Handler是一個基類,它只定義了素有handlers都應該有的接口,同時提供了一些子類能夠直接使用或覆蓋的默認行爲。下面是一些經常使用的Handler:Formater
Formater對象用於配置日誌信息的最終順序、結構和內容。與logging.Handler基類不一樣的是,應用代碼能夠直接實例化Formatter類。另外,若是你的應用程序須要一些特殊的處理行爲,也能夠實現一個Formatter的子類來完成。
Formatter類的構造方法定義以下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
複製代碼
該構造方法接收3個可選參數:
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數量等。
複製代碼
上面文縐縐的說了(複製/粘貼)那麼多,如今應該動手實踐了。
如今我須要既將日誌輸出到控制檯、又能將日誌保存到文件,我應該怎麼辦?
利用剛纔所學的知識,咱們能夠構思一下:
看起來好像也不難,挺簡單的樣子,可是實際如此嗎?
在實際的工做或應用中,咱們或許還須要指定文件存放路徑、用隨機數做爲日誌文件名、顯示具體的信息輸出代碼行數、日誌信息輸出日期和日誌寫入方式等內容。再構思一下:
具體代碼以下:import os
import logging
import uuid
from logging import Handler, FileHandler, StreamHandler
class PathFileHandler(FileHandler):
def __init__(self, path, filename, mode='a', encoding=None, delay=False):
filename = os.fspath(filename)
if not os.path.exists(path):
os.mkdir(path)
self.baseFilename = os.path.join(path, filename)
self.mode = mode
self.encoding = encoding
self.delay = delay
if delay:
Handler.__init__(self)
self.stream = None
else:
StreamHandler.__init__(self, self._open())
class Loggers(object):
# 日誌級別關係映射
level_relations = {
'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING,
'error': logging.ERROR, 'critical': logging.CRITICAL
}
def __init__(self, filename='{uid}.log'.format(uid=uuid.uuid4()), level='info', log_dir='log',
fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
self.logger = logging.getLogger(filename)
abspath = os.path.dirname(os.path.abspath(__file__))
self.directory = os.path.join(abspath, log_dir)
format_str = logging.Formatter(fmt) # 設置日誌格式
self.logger.setLevel(self.level_relations.get(level)) # 設置日誌級別
stream_handler = logging.StreamHandler() # 往屏幕上輸出
stream_handler.setFormatter(format_str)
file_handler = PathFileHandler(path=self.directory, filename=filename, mode='a')
file_handler.setFormatter(format_str)
self.logger.addHandler(stream_handler)
self.logger.addHandler(file_handler)
if __name__ == "__main__":
txt = "關注公衆號【進擊的 Coder】,回覆『日誌代碼』能夠領取文章中完整的代碼以及流程圖"
log = Loggers(level='debug')
log.logger.info(4)
log.logger.info(5)
log.logger.info(txt)
複製代碼
文件保存後運行,運行結果以下圖所示:
日誌確實在控制檯輸出了,再來看一下目錄內是否生成有指定的文件和文件夾:
文件打開後能夠看到裏面輸出的內容:
正確的學習方式是什麼
是一步步的看着文章介紹,等待博主結論?
是拿着代碼運行,跑一遍?
都不是,應該是一邊看着文章,一邊拿着示例代碼琢磨和研究,到底哪裏能夠改進、哪裏能夠設計得更好。若是你須要文章中所用到的示例代碼和流程圖,那麼關注微信公衆號【進擊的 Coder】,回覆『日誌代碼』就能夠領取文章中完整的代碼以及流程圖。畢竟,學習是一件勤勞的事。
參考資料: