對一名開發者來講最糟糕的狀況,莫過於要弄清楚一個不熟悉的應用爲什麼不工做。有時候,你甚至不知道系統運行,是否跟原始設計一致。python
在線運行的應用就是黑盒子,須要被跟蹤監控。最簡單也最重要的方式就是記錄日誌。記錄日誌容許咱們在開發軟件的同時,讓程序在系統運行時發出信息,這些信息對於咱們和系統管理員來講都是有用的。程序員
就像爲未來的程序員寫代碼文檔同樣,咱們應該讓新軟件產生足夠的日誌供系統的開發者和管理員使用。日誌是關於應用運行狀態的系統文件的關鍵部分。給軟件加日誌產生句時,要向給將來維護系統的開發者和管理員寫文檔同樣。web
一些純粹主義者認爲一個受過訓練的開發者使用日誌和測試的時候幾乎不須要交互調試器。若是咱們不能用詳細的日誌解釋開發過程當中的應用,那麼當代碼在線上運行的時候,解釋它們會變得更困難。多線程
這篇文章介紹了 Python 的 logging 模塊,包括它的設計以及針對更多複雜案例的適用方法。這篇文章不是寫給開發者的文檔,它更像是一個指導手冊,來講明 Python 的 logging 模板是如何搭建的,而且激發感興趣的人深刻研究。app
也許會有開發者會問,爲何不是簡單的 print 語句呢? Logging 模塊有不少優點,包括:框架
最後一點,將咱們記錄內容從記錄方式中真正分離,保證了軟件不一樣部分的合做。舉個例子,它容許一個框架或庫的開發者增長日誌而且讓系統管理員或負責運行配置的人員決定稍後應該記錄什麼。ide
Logging 模塊完美地將它的每一個部分的職責分離(遵循 Apache Log4j API 的方法)。讓咱們看看一個日誌線是如何經過這個模塊的代碼,而且研究下它的不一樣部分。函數
記錄器(Logger)工具
記錄器是開發者常常交互的對象。那些主要的 API 說明了咱們想要記錄的內容。測試
舉個記錄器的例子,咱們能夠分類請求發出一條信息,而不用擔憂它們是如何從哪裏被髮出的。
好比,當咱們寫下 logger.info(「Stock was sold at %s」, price) 咱們在頭腦中就有以下模塊:
咱們須要一條線。假設有些代碼在記錄器中運行,讓這條線出如今控制檯或文件中。可是在內部實際發生了什麼呢?
日誌記錄
日誌記錄是 logging 模塊用來知足全部需求信息的包。它們包含了須要記錄日誌的地方、變化的字符串、參數、請求的信息隊列等信息。
它們都是被記錄的對象。每次咱們調用記錄器時,都會生成這些對象。但這些對象是如何序列化到流中的呢?經過處理器!
處理器
處理器將日誌記錄發送給其餘輸出終端,他們獲取日誌記錄並用相關函數中處理它們。
好比,一個文件處理器將會獲取一條日誌記錄,而且把它添加到文件中。
標準的 logging 模塊已經具有了多種內置的處理器,例如:
多種文件處理器(TimeRotated, SizeRotated, Watched),能夠寫入文件中
目前咱們有個相似於真實狀況的模型:
大部分的處理器都在處理字符串(SMTPHandler和FileHandler等)。或許你想知道這些結構化的日誌記錄是如何轉變爲易於序列化的字節的。
格式器
格式器負責將豐富的元數據日誌記錄轉換爲字符串,若是什麼都沒有提供,將會有個默認的格式器。
通常的格式器類由 logging 庫提供,採用模板和風格做爲輸入。而後佔位符能夠在一個 LogRecord 對象中聲明全部屬性。
好比:’%(asctime)s %(levelname)s %(name)s: %(message)s’ 將會生成日誌相似於 2017-07-19 15:31:13,942 INFO parent.child: Hello EuroPython.
請注意:屬性信息是經過提供的參數對日誌的原始模板進行插值的結果。(好比,對於 logger.info(「Hello %s」, 「Laszlo」) 這條信息將會是 「Hello Laszlo」)
全部默認的屬性均可以在日誌文檔中找到。
好了,如今咱們瞭解了格式器,咱們的模型又發生了變化:
過濾器
咱們日誌工具的最後一個對象就是過濾器。
過濾器容許對應該發送的日誌記錄進行細粒度控制。多種過濾器能同時應用在記錄器和處理器中。對於一條發送的日誌來講,全部的過濾器都應該經過這條記錄。
用戶能夠聲明他們本身的過濾器做爲對象,使用 filter 方法獲取日誌記錄做爲輸入,反饋 True / False 做爲輸出。
出於這種考慮,如下是當前的日誌工做流:
記錄器層級
此時,你可能會對大量複雜的內容和巧妙隱藏的模塊配置印象深入,可是還有更須要考慮的:記錄器分層。
咱們能夠經過 logging.getLogger() 建立一個記錄器。這條字符向 getLogger 傳遞了一個參數,這個參數能夠經過使用圓點分隔元素來定義一個層級。
舉個例子,logging.getLogger(「parent.child」) 將會建立一個 「child」 的記錄器,它的父級記錄器叫作 「parent.」 記錄器是被 logging 模塊管理的全局對象,因此咱們能夠方便地在項目中的任何地方檢索他們。
記錄器的例子一般也被認爲是渠道。層級容許開發者去定義渠道和他們的層級。
在日誌記錄被傳遞到全部記錄器內的處理器時,父級處理器將會進行遞歸處理,直到咱們到達頂級的記錄器(被定義爲一個空字符串),或者有一個記錄器設置了 propagate = False。咱們可經過更新的圖中看出:
請注意父級記錄器沒有被調用,只有它的處理器被調用。這意味着過濾器和其餘在記錄器類中的代碼不會在父級中被執行。當咱們在記錄器中增長過濾器時,這一般是個陷阱。
咱們已經闡明過職責的劃分以及咱們是如何微調日誌過濾。然而仍是有兩個其餘的屬性咱們沒有說起:
舉個例子,當一個記錄器被設置爲 INFO 的等級,只有 INFO 等級及以上的纔會被傳遞,一樣的規則適用於處理器。
基於以上全部的考慮,最後的日誌記錄的流程圖看起來像這樣:
如今咱們已經瞭解了 logging 模塊的部分及設計,是時候去了解一個開發者是如何與它交互的了。如下是一個代碼例子:
它用模塊 __ name __ 建立了一個日誌記錄器。它會基於項目結構建立渠道和等級,正如 Pyhon 模塊用圓點鏈接同樣。
記錄器變量引用記錄器的 「module」 ,用 「projectA」 做爲父級, 「root」 做爲父級的父級。
在第五行,咱們看到如何執行調用去發送日誌。咱們能夠用 debug 、 info 、error 或 critical 這些方法之一在合適的等級上去記錄日誌。
當記錄一條信息時,除了模板參數,咱們能夠經過特殊的含義傳遞密碼參數,最有意思的是 exc_info 和 stack_info。它們將會分別增長關於當前異常和棧幀的信息。爲了方便起見,在記錄器對象中有一個方法異常,正如這個錯誤調用 exc_info=True 。
這些是如何使用記錄器模塊的基礎,可是有些一般被認爲是不良操做的作法一樣值得說明。
過分格式化字符串
應該儘可能避免使用 loggger.info(「string template {}」.format(argument)) ,可能的話儘可能使用 logger.info(「string template %s」, argument)。 這是個更好的實踐,由於只有當日志被髮送時,字符串纔會發生真正改變。當咱們記錄的層級在 INFO 之上時,不這麼作會致使浪費週期,由於這個改變仍然會發生。
捕捉和格式化異常
一般,咱們想記錄在抓取模塊異常的日誌信息,若是這樣寫會很直觀:
可是這樣的代碼會給咱們顯示相似於 Something bad happened: 「secret_key.」 的日誌行,這並非頗有用。若是咱們使用 exc_info 做爲事先說明,那麼它將會以下顯示:
這不只僅會包含異常的準確資源,同時也會包含它的類型。
裝備咱們的軟件很簡單,咱們須要設置日誌棧,而且制定這些記錄是如何被髮出的。
如下是設置日誌棧的多種方法
基礎設置
這是至今最簡單的設置日誌記錄的方法。使用 logging.basicConfig(level=」INFO」) 搭建一個基礎的 StreamHandler ,這樣就會記錄在 INFO 上的任何東西,而且到控制檯以上的級別。如下是編寫基礎設置的一些參數:
參數 | 說明 | 舉例 |
filename | 指定建立的文件處理器,使用特定的文件名,而不是流處理器 | /var/logs/logs.txt |
format | 爲處理器使用特定格式的字符串 | 「‘%(asctime)s %(message)s'」 |
datefmt | 使用特定的日期/時間格式 | 「%H:%M:%S」 |
level | 爲根記錄器等級設置特定等級 | 「INFO」 |
在設置簡單的腳本上,這是簡單又使用的方法。
請注意, basicConfig 僅僅在運行的一開始能夠這麼調用。若是你已經設置了你的根記錄器,調用 basicConfig 將不會奏效。
字典設置
全部元素的設置以及如何鏈接它們能夠做爲字典來講明。這個字典應當由不一樣的部分組成,包括記錄器、處理器、格式化以及一些基本的通用參數。
例子以下:
當被引用時, dictConfig 將會禁用全部運行的記錄器,除非 disable_existing_loggers 被設置爲 false。這一般是須要的,由於不少模塊聲明瞭一個全球記錄器,它在 dictConfig 被調用以前被導入的時候將會實例化。
你能夠查看 schema that can be used for the dictConfig method(連接)。一般,這些設置將會存儲在一個 YAML 文件中,而且從那裏設置。不少開發者會傾向於使用這種方式而不是使用 fileConfig(連接),由於它爲定製化提供了更好的支持。
幸好設計了這種方式,拓展 logging 模塊很容易。讓咱們來看些例子:
logging JSON | 記錄 JSON
只要咱們想要記錄,咱們能夠經過建立一種自定義格式化來記錄 JSON ,它會將日誌記錄轉化爲 JSON 編碼的字符串。
添加更多上下文
在格式化中,咱們能夠指定任何日誌記錄的屬性。
咱們能夠經過多種方式增長屬性,在這個例子中,咱們用過濾器來豐富日誌記錄。
這樣有效地在全部日誌記錄中增長了一個屬性,它能夠經過記錄器。格式化會在日誌行中包含這個屬性。
請注意這會在你的應用中影響全部的日誌記錄,包含你可能用到以及你發送日誌的庫和其餘的框架。它能夠用來記錄相似於在全部日誌行裏的一個獨立請求 ID ,去追蹤請求或者去添加額外的上下文信息。
從 Python 3.2 開始,你可使用 setLogRecordFactory 去得到全部日誌的建立記錄和增長額外的信息。這個 extra attribute 和 LoggerAdapter class 或許一樣是有趣的。
緩衝日誌
有時候當錯誤發生時,咱們想要排除日誌故障。建立一個緩衝的處理器,來記錄當錯誤發生時的最新故障信息是一種可行的辦法。下面的代碼是個非人爲策劃的例子:
更多信息
這篇關於日誌記錄庫的靈活性和可配置性的介紹,目的在於證實它如何設計了分別的關注點的美學。它一樣爲任何對 logging documentation 和 how-to guide 感興趣的人提供了一個堅實的基礎。雖然這篇文章對於 Python 日誌模塊並非一個綜合性的知道,可是這裏有一些針對於常見的問題的回答。
問:個人庫發送了一個「 no logger configured」 的警告
答:從 The Hitchhiker’s Guide to Python 查閱 how to configure logging in a library
問:若是一個記錄器沒有層級設置會怎麼樣?
答:記錄器的有效層級,會由它的父級遞歸定義。
問:我全部的日誌都在本地時間,我如何記錄在 UTC ?
答:格式化就是答案!你須要在你的格式化中設置 converter 屬性爲通用的 UTC 時間。使用 converter = time.gmtime 。
此文轉載文,著做權歸做者全部,若有侵權聯繫小編刪除!
原文地址:https://www.tuicool.com/articles/q2MZniF
須要源代碼的或者想了解更多的 (點擊這裏查看)