原文連接:5 Techniques to Improve Your Server Logginghtml
如下爲譯文服務器
最近涌現出各類各樣能幫助你理解日誌的新工具,有相似 Scribe、Logstash 這樣的開源項目,也有相似 Splunk 的預付費工具,還有託管服務如 SumoLogic 和 PaperTrail。這些工具的共同點是對日誌數據進行清洗,在大量日誌中提取一些更有價值的文件。網絡
但有一件事這些工具卻心有餘而力不足,由於它們徹底依賴你實際投入的日誌數據,而如何保證數據的質量和數量則須要用戶自行完成。所以,在關鍵時刻,若是你須要基於部分或者遺漏日誌作代碼調試時,事情可能會變得很是棘手。多線程
爲了減小這種狀況發生,在這裏分享五個建議,在你記錄日誌時最好能銘記於心:架構
正如 Ringo,線程名稱這個屬性是 Java 中最被低估的方法之一。其緣由是線程名稱大部分是描述性的。然而問題一樣出如今這裏,相似人們本身,起名時一般會被賦予必定的意義。而在多線程日誌中,線程名一樣揮着關鍵做用。一般狀況下,大多很多天志框架會記錄當前所調用的線程名稱。可悲的是,咱們一般會看到 http-nio-8080-exec-3
這種名字,簡單地由線程池或容器進行分配。app
出於某種緣由,咱們曾不止一次地聽過這種誤解——線程名稱是不可變的。與之相反,在日誌中,線程名稱佔據基本主要地位,你應該確保能正確使用。好比將它與具體情境結合起來,例如 Servlet 的名字、任務相關,或者一些動態語境如用戶或消息 ID。框架
這樣的話,代碼接口應該是這樣:分佈式
Thread.currentThread().setName(ProcessTask.class.getName() + 「: 「+ message.getID);
更先進的版本將被加載到當前線程的線程局部變量,配置 log appender,並自動將其添加到日誌條目。工具
當多個線程寫入服務器日誌,但你須要集中在單一線程上時,這將會很是有用。若是你在一個分佈式 /SOA 環境下運行,更能看到它得天獨厚的優點。this
在 SOA 或消息驅動的架構,任務執行極可能跨多臺機器。當處理這種環境下的故障時,鏈接相關機器和它們的狀態將是瞭解具體狀況的關鍵。大多很多天志分析器會將這些日誌信息分組,假設你爲它們提供了惟一標識,它們即可以做爲實際日誌消息的一部分。
從設計的角度出發,這意味着,從進入系統到操做完成,每個入站操做應該有其惟一的 ID 對應。請注意,一個持久的標識符,如用戶 ID 可能不是一個好容器。在記錄日誌文件的過程當中,用戶可能有多個操做,這將使得隔離特定流更加困難。UUIDs 多是個不錯的選擇。它的值能夠被加載到實際線程名稱或者做爲 TLS-thread 的局部儲存器。
不少時候,你會看到一段代碼在緊密的循環中運行,並執行相應的日誌操做。基本假設是,該代碼運行的次數是有限的。
極可能運行狀況很是良好。可是當代碼獲得意外輸入時,循環可能並不會中斷。在這種狀況下,你不僅是處理一個無限循環「雖然這樣已經很糟糕了」,你正在處理的代碼正將無限量的數據寫到磁盤或網絡。
在單機場景中它可能會形成一臺服務器崩潰,而在分佈式場景中,被影響的則是整個集羣。所以若是可能,不要在緊密循環中記錄日誌。捕獲錯誤時,這一點尤爲如此。
下面這個例子,記錄了一個 while 循環中的異常:
void read() { while (hasNext()) { try { readData(); } catch {Exception e) { // this isn’t recommend logger.error(「error reading data「, e); } } }
若是 readData 拋出異常,而 hasNext 返回值爲 true,這裏將會寫入無限量的日誌數據。要解決這個問題的方法是確保不會記錄這一切:
void read() { int exceptionsThrown = 0; while (hasNext()) { try { readData(); } catch {Exception e) { if (exceptionsThrown < THRESHOLD) { logger.error(「error reading data", e); exceptionsThrown++; } else { // Now the error won’t choke the system. } } } }
另外一種方法是從循環中移除日誌記錄,並保存第一/最後一個異常對象並在其它地方記錄。
Westeros 有最後一道防護牆,而你有 Thread.uncaughtExceptionHandler
。所以,儘可能使用它們。若是沒有安裝這些處理程序,在異常拋出時,你只能得到不多有價值的上下文,同時你也沒法控制在結束以前你已經將其記錄,並肯定記錄的位置。
請注意,即便在未捕獲的異常處理程序,看起來你沒有任何辦法訪問線程中(已終止)的任何變量,你仍然能夠得到實際線程對象的引用。若是你堅持# 1步,你仍然會獲得一個有意義的thread.getName()
值可記錄。
每當調用一個外部的 API, JVM 異常的概率將大大增長。這包括 Web 服務、 HTTP、 DB、 文件系統、操做系統和任何其餘 JNI 調用。認真對待每一個調用,由於它隨時會爆炸 「它頗有可能發生在一樣的點」。
大多數狀況下,外部 API 故障的緣由是意外輸入,日誌中對其記錄是修復代碼的關鍵。
在這一點上,你能夠選擇不記錄錯誤,只是拋出異常也能夠。在這種狀況下,只要收集到調用的相關參數,並將其解析爲異常錯誤信息。
只要確保異常被捕獲並記錄在更高級別的堆棧調用便可。
try { return s3client.generatePresignedUrl(request); } catch (Exception e) { String err = String.format(「Error generating request: %s bucket: %s key: %s. method: %s", request, bucket, path, method); log.error(err, e); //you can also throw a nested exception here with err instead. }