
?
對一名開發者來說最糟糕的情況,莫過于要弄清楚一個不熟悉的應用為何不作業,有時候,你甚至不知道系統運行,是否跟原始設計一致,
在線運行的應用就是黑盒子,需要被跟蹤監控,最簡單也最重要的方式就是記錄日志,記錄日志允許我們在開發軟體的同時,讓程式在系統運行時發出資訊,這些資訊對于我們和系統管理員來說都是有用的,
就像為將來的程式員寫代碼檔案一樣,我們應該讓新軟體產生足夠的日志供系統的開發者和管理員使用,日志是關于應用運行狀態的系統檔案的關鍵部分,給軟體加日志產生句時,要向給未來維護系統的開發者和管理員寫檔案一樣,
一些純粹主義者認為一個受過訓練的開發者使用日志和測驗的時候幾乎不需要互動除錯器,如果我們不能用詳細的日志解釋開發程序中的應用,那么當代碼在線上運行的時候,解釋它們會變得更困難,
這篇文章介紹了 Python 的 logging 模塊,包括它的設計以及針對更多復雜案例的適用方法,這篇文章不是寫給開發者的檔案,它更像是一個指導手冊,來說明 Python 的 logging 模板是如何搭建的,并且激發感興趣的人深入研究,
為什么使用 logging 模塊?
也許會有開發者會問,為什么不是簡單的 print 陳述句呢? Logging 模塊有很多優勢,包括:
- 多執行緒支持
- 通過不同級別的日志分類
- 靈活性和可配置性
- 將如何記錄日志與記錄什么內容分離
最后一點,將我們記錄內容從記錄方式中真正分離,保證了軟體不同部分的合作,舉個例子,它允許一個框架或庫的開發者增加日志并且讓系統管理員或負責運行配置的人員決定稍后應該記錄什么,
Logging 模塊中有什么
Logging 模塊完美地將它的每個部分的職責分離(遵循 Apache Log4j API 的方法),讓我們看看一個日志線是如何通過這個模塊的代碼,并且研究下它的不同部分,
記錄器(Logger)
記錄器是開發者經常互動的物件,那些主要的 API 說明了我們想要記錄的內容,
舉個記錄器的例子,我們可以分類請求發出一條資訊,而不用擔心它們是如何從哪里被發出的,
比如,當我們寫下 logger.info(“Stock was sold at %s”, price) 我們在頭腦中就有如下模塊:

?
我們需要一條線,假設有些代碼在記錄器中運行,讓這條線出現在控制臺或檔案中,但是在內部實際發生了什么呢?
日志記錄
日志記錄是 logging 模塊用來滿足所有需求資訊的包,它們包含了需要記錄日志的地方、變化的字串、引數、請求的資訊佇列等資訊,
它們都是被記錄的物件,每次我們呼叫記錄器時,都會生成這些物件,但這些物件是如何序列化到流中的呢?通過處理器!
處理器
處理器將日志記錄發送給其他輸出終端,他們獲取日志記錄并用相關函式中處理它們,
比如,一個檔案處理器將會獲取一條日志記錄,并且把它添加到檔案中,
標準的 logging 模塊已經具備了多種內置的處理器,例如:
多種檔案處理器(TimeRotated, SizeRotated, Watched),可以寫入檔案中
- StreamHandler 輸出目標流比如 stdout 或 stderr
- SMTPHandler 通過 email 發送日志記錄
- SocketHandler 將日志檔案發送到流套接字
- SyslogHandler、NTEventHandler、HTTPHandler及MemoryHandler等
目前我們有個類似于真實情況的模型:

?
大部分的處理器都在處理字串(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 模塊很容易,讓我們來看些例子:
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
需要源代碼的或者想了解更多的 (點擊這里查看)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/142373.html
標籤:其他
下一篇:python 打包exe出現RuntimeError: Could not find the matplotlib data files 的解決方法
