日誌記錄是軟體開發和系統維運的關鍵環節,有效的日誌策略能顯著提升系統的可觀測性和問題診斷效率。本文從日誌結構化、框架選擇到效能最佳化,提供全面的日誌最佳實踐,並探討如何將 Fluentd 與不同程式語言和日誌框架整合,構建更強大的日誌管理系統。實務上,開發者常需根據專案規模和需求選擇合適的日誌框架,並考量日誌格式、輸出目標、安全性等因素。透過整合 Fluentd 等日誌收集工具,可以集中管理和分析日誌資料,進一步提升系統的可觀測性和問題排查效率。
日誌最佳實踐
在軟體開發中,日誌記錄是一項重要的任務,它能夠幫助開發人員和維運團隊瞭解系統的執行狀態、除錯問題以及進行安全稽核。良好的日誌記錄實踐可以提高系統的可觀察性、可維護性和安全性。
日誌結構化
日誌的結構化不僅僅是指日誌內容的格式,還包括時間戳、日誌級別、位置、執行緒 ID 等後設資料的組織。業界有一些公認的日誌格式,如圖 10.3 所示,這些格式通常與應用邏輯相關。
圖 10.3 日誌事件的組成要素
圖 10.3 展示了構成優秀日誌事件的幾個關鍵要素,包括:
- 上下文 - 誰(Context—Who):與事件相關的使用者或系統資訊。
- 上下文 - 做什麼(Context—What):事件的具體內容或操作。
- 上下文 - 何時(Context—When):事件發生的時間戳。
- 上下文 - 為什麼(Context—Why):事件發生的原因或背景。
- 上下文 - 何地(Context—Where):事件發生的地點或系統位置。
- 開發實踐(Development practices):與日誌相關的開發習慣或規範。
- 錯誤程式碼使用(Use of error codes):使用標準化的錯誤程式碼來表示不同型別的錯誤。
- 人類可讀性(Human readable):日誌內容應易於人類理解。
- 機器可讀性/結構化(Machine readable / structured):日誌應具有結構化格式,以便於機器解析。
- 語言清晰度(Clarity of language):使用清晰、簡潔的語言來描述事件。
為應用程式準備日誌
在應用程式即將發布時,需要確保日誌系統已經準備就緒,以便於後續的維護和除錯。這包括評估當前日誌事件的品質,根據圖 10.3 中的要素進行改進,並估計完成這些改進所需的時間。
重構日誌事件
透過搜尋目前活躍的程式碼函式庫中的日誌事件,記錄事件型別,並根據圖 10.3 中的不同因素評估日誌事件,可以估計出改進日誌事件所需的時間。如果時間緊迫,建議採用自頂向下的方法(如圖 10.2 所示)。
使用日誌框架
大多數程式語言都提供了日誌框架,無論是作為基礎語言的一部分還是作為函式庫。使用日誌框架可以提高日誌的一致性、結構化和可組態性,並且可以減少解析文字輸出的工作量,從而節省處理能力。
日誌框架的優勢
使用日誌框架的好處包括:
- 一致性:在日誌資料中保持一致性,包括日誌級別和日誌條目的結構。
- 額外的上下文資訊:框架可以管理額外的上下文資訊,如 OpenTracing 的追蹤上下文值。
- 可組態性:透過組態控制需要關注的日誌內容,調整日誌的詳細程度。
- 容器化環境中的優勢:在容器化環境中,使用結構化的日誌輸出可以避免重新解析文字輸出的開銷。
不良開發實踐
某些開發實踐可能會對日誌系統產生負面影響,例如:
- 重新丟擲異常(Rethrowing exceptions):捕捉並重新丟擲異常可能會導致多個日誌事件記錄相同的問題,增加分析和除錯的難度。
- 使用標準異常和錯誤結構(Using standard exceptions and error structures):雖然使用語言預定義的異常類別可以提高程式碼的可重用性和可讀性,但可能會使日誌分析變得更加困難,因為難以區分是防禦性編碼還是潛在的錯誤。
通知風暴
當日誌事件與通知機制(如電子郵件或 Slack)相關聯時,需要注意通知風暴的問題。如果同一個錯誤不斷發生,可能會導致通知通路被相同的資訊淹沒,從而導致使用者關閉通知或系統將應用程式視為垃圾郵件傳送者。
防止通知風暴
可以使用過濾器(如 Fluentd 中的過濾器)或日誌框架本身的功能(如 Log4j2 的 BurstFilter)來抑制通知風暴。
日誌最佳實踐與日誌框架的應用
在軟體開發過程中,日誌(Logging)是至關重要的環節,它能夠幫助開發者瞭解系統的執行狀況、追蹤錯誤、以及進行問題的診斷。本篇文章將探討日誌最佳實踐和日誌框架的應用,以提高系統的可維護性和可觀測性。
為什麼需要良好的日誌實踐?
良好的日誌實踐可以讓開發者更容易地理解系統的執行狀況和診斷問題。以下是一些需要關注的重點:
- 使用清晰、簡單的語言來描述日誌事件
- 在適當的時候使用錯誤程式碼來標識日誌事件
- 日誌事件應該包含上下文資訊,例如伺服器名稱、程式ID等
- 需要遵守相關的立法和組織規定,確保日誌資料的安全性和保密性
日誌框架的選擇和應用
大多數程式語言都提供了日誌框架(Logging Framework),這些框架可以幫助開發者更方便地記錄和管理日誌。以下是一些常見的日誌框架:
- 不同的程式語言有不同的日誌框架,例如Java的Log4j、Logback等
- 選擇合適的日誌框架可以提高系統的可維護性和可觀測性
選擇日誌框架的考量因素
在選擇日誌框架時,需要考慮以下因素:
- 日誌框架的功能和特性,例如日誌級別、日誌格式等
- 日誌框架的效能和可擴充套件性
- 日誌框架與其他系統元件的整合,例如Fluentd等
如何最佳化日誌效能?
在記錄日誌時,需要考慮效能的問題。以下是一些最佳化日誌效能的方法:
使用Lambda或懶執行能力
現代程式語言大多支援Lambda或懶執行能力,這可以讓開發者寫出更高效的日誌記錄程式碼。例如:
LOGGER.atDebug().log("{\"attribute\":\"" + aStringValue + "\",\"array\":" + aArrayOfKeyValues.toJSON + "}");
這種寫法可以避免不必要的字串拼接和計算,從而提高效能。
使用守衛函式(Guard Functions)
守衛函式可以用來判斷是否需要記錄日誌,從而避免不必要的計算。例如:
Logger.ifDebug {
myLogMessage = "{\"attribute\":\"" + aStringValue + "\",\"array\":" + aArrayOfKeyValues.toJSON + "}";
Logger.debug(myLogMessage);
}
這種寫法可以避免在不需要記錄日誌時進行不必要的字串拼接和計算。
重點回顧
- 良好的日誌實踐可以讓開發者更容易地理解系統的執行狀況和診斷問題
- 日誌框架可以幫助開發者更方便地記錄和管理日誌
- 需要遵守相關的立法和組織規定,確保日誌資料的安全性和保密性
- 最佳化日誌效能可以提高系統的整體效能
參考資料
- Quarkus和GraalVM相關資料:http://mng.bz/zQ5Q、http://mng.bz/0w96、www.graalvm.org/、https://quarkus.io/
更多資訊
更多關於Quarkus和GraalVM的資訊,請參閱相關資料。這些技術可以幫助開發者建立更高效、更可擴充套件的系統。
日誌框架的價值與結構
日誌框架在軟體開發中扮演著至關重要的角色,無論其起源如何,它們都解決了以下幾個關鍵主題:
- 提供簡單的方式以日誌級別分類別輸出日誌事件
- 允許透過組態控制傳送的日誌事件
- 將日誌事件導向不同的輸出形式,如檔案、stdout、HTTP等
日誌級別的概念可以追溯到Syslog標準(RFC 5424),但對日誌函式庫影響最深遠的還是Apache Log4J。Log4J的設計和實作在多種語言中被移植,其API和特性在許多其他語言的日誌框架中都能看到相似之處。
日誌框架的典型結構
以Log4J為例,日誌框架通常包含以下元件:
- Logger Context(日誌上下文)
- Configuration(組態)
- Filter(過濾器)
- Logger(日誌記錄器)
- Logger Config(日誌組態)
- Formatter(格式化器)
- Appender(輸出器)
11.2.1 日誌上下文(Logger Context)
日誌上下文是框架在應用程式中的基礎,負責持有對特定日誌物件的參照,並處理組態檔案以建立必要的日誌物件。它是應用程式內所有日誌元素的“一站式服務”,用於檢索處理日誌事件的物件。
11.2.2 輸出器(Appender)
輸出器的任務最容易理解,它負責將日誌事件傳送到適當的目的地,如透過TCP/IP傳輸、使用API呼叫服務(如Logstash)、或將日誌事件追加到檔案的末尾。每個輸出器都會使用過濾器來控制需要追加的日誌事件,並可能使用格式化器來轉換事件的內部表示形式。
11.2.3 日誌記錄器(Logger)
可以定義多個日誌記錄器,以便應用程式的不同部分以不同的方式使用日誌記錄。例如,為記錄官方稽核事件與一般的應用程式稽核跟蹤使用不同的日誌記錄器。這些日誌記錄器可以在程式碼中選擇,並具有不同的組態,如使用哪個輸出器、應用哪些過濾器等。
程式碼範例與解析
以下是一個簡單的Log4J組態範例:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class HelloWorld {
private static final Logger logger = LogManager.getLogger(HelloWorld.class);
public static void main(String[] args) {
logger.info("Hello, World!");
}
}
內容解密:
import陳述式匯入了Log4J所需的類別。LogManager.getLogger(HelloWorld.class)取得了一個與HelloWorld類別相關聯的日誌記錄器。logger.info("Hello, World!")使用INFO級別記錄了一條日誌訊息。
這個範例展示瞭如何在Java應用程式中使用Log4J進行日誌記錄。透過組態不同的輸出器和過濾器,可以控制日誌事件的去向和級別。
圖表解析
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title 日誌最佳實踐與框架整合應用
package "系統架構" {
package "前端層" {
component [使用者介面] as ui
component [API 客戶端] as client
}
package "後端層" {
component [API 服務] as api
component [業務邏輯] as logic
component [資料存取] as dao
}
package "資料層" {
database [主資料庫] as db
database [快取] as cache
}
}
ui --> client : 使用者操作
client --> api : HTTP 請求
api --> logic : 處理邏輯
logic --> dao : 資料操作
dao --> db : 持久化
dao --> cache : 快取
note right of api
RESTful API
或 GraphQL
end note
@enduml此圖示展示了日誌框架中各元件之間的關係。日誌上下文與組態和日誌記錄器相關聯,日誌記錄器可以有多個輸出器,而輸出器又可以使用過濾器和格式化器。
日誌框架的選擇與評估
在評估採用日誌框架時,需要考慮多個因素以選擇最合適的框架。我們制定了一系列問題來幫助評估需求並選擇符合需求的框架。這些問題有助於確定日誌框架的優先順序,使得評估框架與需求的匹配程度變得更加容易。
選擇框架時需要考慮的問題
可用的 Appender 型別:是否僅限於某種型別的 Appender,例如檔案?是否有可直接與日誌統一解決方案(如 Fluentd 或 Logstash)配合使用的 Appender?
Appender 行為的客製化:是否能夠客製化或最佳化 Appender 的行為?例如,日誌輪替或網路埠和位址是否可組態?
日誌事件輸出的客製化:是否能夠根據應用程式的不同部分客製化日誌事件的輸出?例如,應用程式框架的日誌閾值(如 Spring 或 Core .Net)設定為 Warning 和 Error,但自定義邏輯可以將閾值設定為 Info。
日誌組態的調整難易度:不使用程式碼的情況下,調整日誌組態的難易度如何?理想情況下,應能夠更新或覆寫預設的日誌組態,以選擇性地取得更多資訊。
框架自動提供資訊的能力:框架能夠自動提供多少資訊(例如,為追蹤點提供方法和類別名稱)以及正確結構的時間戳記?
日誌輸出格式的客製化:是否能夠客製化日誌輸出的格式(例如,JSON、XML)?這反映了上一章中提到的最佳日誌應具備的結構,使日誌事件既可被人讀取,也可被機器讀取。
框架的足跡大小:框架的足跡有多緊湊(對於 IoT 使用案例至關重要)?對於 IoT 和行動解決方案,需要緊湊的足跡以限制資源使用。
日誌輸出的安全性:是否能夠使日誌輸出安全——使用 TLS、加密檔案等?安全性是否足以處理所處理的資料?
對應用程式效能的影響:框架是否會對應用程式的吞吐量/效能產生實質影響,特別是在最終的 I/O 階段?日誌記錄是否可能成為執行緒阻塞機制?
API 的易用性:日誌框架的 API 是否易於使用?如果應用程式碼中的呼叫難以使用,開發人員可能會避免建立日誌事件。理想情況下,介面應直觀,但擁有良好的支援檔案可以非常有價值,特別是對於剛開始其開發生涯的人。
縮小選擇範圍
在評估每一個可能的選項之前,試圖縮小選擇範圍是值得的。附錄 E 中的表 E.11 可以提供幫助,因為它們反映了我們認為最重要的和/或最主流的日誌框架。
日誌框架的重要組成部分
Appender 結構
Appender 通常透過繼承或封裝層次結構來建立,以便每個複雜度層次可以利用更簡單的操作。最終,這將取決於標準介面定義,以便無論是哪個 Appender,它們都以相同的方式協同工作。
過濾器(Filter)
過濾器決定哪些日誌事件應該被發出,主要透過確定日誌事件是否高於或低於組態中設定的閾值。由於過濾器與 Appender 相關聯,因此不同的日誌目的地可以組態不同的日誌級別。
格式化器(Formatter)
格式化器的任務是建構 Appender 輸出,以便按照想要或需要的方式呈現日誌條目(例如,以 12 小時或 24 小時格式顯示時間)。一些 Appender 將允許靈活性(例如,檔案 Appender)。
組態(Configuration)
通常,我們希望透過組態而不是程式碼來驅動應用程式的日誌記錄,因為這允許在不一定進行侵入式程式碼更改的情況下組態日誌記錄。這也使得驗證組態的周轉更快。它允許我們根據佈署上下文更改日誌處理方式。
Fluentd 日誌框架的整合與應用
在前面的章節中,我們探討了日誌框架的重要性以及 Fluentd 的基本使用方法。本章節將探討如何將 Fluentd 與不同的程式語言和日誌框架整合,以實作高效的日誌收集和管理。
選擇適合的日誌框架
在評估當前使用的日誌框架是否適合未來需求時,需要考慮多個因素。如果目前的日誌框架無法滿足需求,則可能需要考慮更換或擴充套件新的日誌框架。附錄 E 中提供了一些可替代的日誌框架選項。
Fluentd 的日誌函式庫和 Appenders
如果目前使用的日誌框架不支援 Fluentd,可以透過使用 Fluentd 提供的函式庫來解決。這些函式庫支援多種程式語言,包括 Erlang、Go、Java、Node.js、OCaml、Perl、PHP、Python、Ruby 和 Scala。表 11.1 列出了 Fluentd 對不同程式語言的支援情況。
表 11.1:Fluentd 對不同程式語言的支援
直接使用 Fluentd 日誌函式庫的優缺點
直接使用 Fluentd 日誌函式庫有其優缺點,如表 11.2 所示。
表 11.2:直接使用 Fluentd 日誌函式庫的優缺點
| 優點 | 缺點 |
|---|---|
| 精簡的程式碼,只需提供輸出到 Fluentd 的功能。 | 被限制在只能使用 Fluentd,對於封裝的解決方案,不嘗試強制其日誌工作方式與提供的選項不同。 |
直接將日誌傳送到 Fluentd
除了使用日誌框架和 Fluentd 日誌函式庫外,還可以直接透過 TCP/IP 或 HTTP(s) 將日誌傳送到 Fluentd。這種方式不需要額外的函式庫依賴,只需基本的網路功能。
使用 Python 與日誌框架整合 Fluentd
為了展示如何將 Fluentd 與日誌框架整合,我們以 Python 為例。Python 支援使用 Fluentd 函式庫與其原生日誌框架整合,提供了一個理想的抽象化連線。
import logging
from fluent import handler
# 設定日誌框架
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('fluent-test')
# 設定 Fluentd 處理器
fluent_handler = handler.FluentHandler('app.log', host='localhost', port=24224)
logger.addHandler(fluent_handler)
# 記錄日誌事件
logger.info('這是一個測試日誌事件')
內容解密:
- 匯入必要的模組:首先,我們匯入
logging模組和fluent.handler,以便使用 Python 的原生日誌框架和 Fluentd 的處理器。 - 設定日誌框架:我們設定了日誌的基本組態,並建立了一個名為
fluent-test的日誌記錄器。 - 設定 Fluentd 處理器:建立了一個
FluentHandler例項,指定了標籤app.log和 Fluentd 的主機及埠,並將其新增到日誌記錄器中。 - 記錄日誌事件:最後,我們使用
logger.info方法記錄了一個簡單的日誌事件。