本文爲《剖析 | SOFABoot 框架》第二篇,本篇作者盲僧,來自遨遊酒店信息技術。《剖析 | SOFABoot 框架》系列由 SOFA 團隊和源碼愛好者們出品,項目代號:<SOFA:BootLab/>,文章尾部有參與方式,歡迎同樣對源碼熱情的你加入。

SOFABoot 是螞蟻金服開源的基於 SpringBoot 的研發框架,提供了諸如 Readiness Check、類隔離、日誌空間隔離等能力,用於快速、敏捷地開發 Spring 應用程序,特別適合構建微服務系統。

本文將從 Java 的日誌體系談起,對 JCL、SLF4J 兩個經典的日誌框架做一個闡述,引出 SOFABoot 開源的日誌隔離框架 sofa-common-tools ,並且有實戰 Demo,能夠幫助我們快速上手和了解這款框架的使用和作用,最後從源碼角度對其進行分析,不僅知其然,還要知其所以然。

SOFABoot : https://github.com/sofastack/sofa-boot

sofa-common-tools : https://github.com/sofastack/sofa-common-tools

1 Java 日誌問題

業務開發對日誌的選擇

衆所周知,Java 的日誌體系非常複雜,有 Log4j、Log4j2、Logback、JUL 等實現,這麼多的日誌實現讓開發人員在選擇上不得不犯暈,因爲每個日誌實現都對外提供了不同的 API,而且還要擔心與項目中現有的第三方框架依賴的日誌實現產生衝突問題,甚至還要去維護第三方框架帶來的日誌依賴。在這些問題的基礎上,Java 日誌框架應運而生,典型的有 JCL 和 SLF4J。

JCL

JCL 即 Apache Commons Logging,它的原理是提供了一套接口,用戶使用了它的接口進行編程,具體實現交由它的 LogFactoryImpl 去動態查找, 但是它並不能綁定所有的日誌實現,因爲查找綁定的日誌實現是放在 classesToDiscover 數組裏寫死的,導致擴展起來比較麻煩,當前最新版本是 1.2 版本,還不支持綁定 Log4j2 和 Logback。

SLF4J

於是乎,大名鼎鼎的 SLF4J 出現了,它的存在就是爲了替換 JCL,所以肯定提供了比 JCL 更強大的功能。同樣是面向接口編程的設計,但是 SLF4J 充分考慮到了後期的擴展問題:一旦市面上有新的日誌實現,那麼只需要提供新的綁定包即可,相對於 JCL 的動態綁定,SLF4J 實際上是靜態綁定,因爲應用程序具體要選用哪種日誌組件是由開發人員使用哪個綁定包決定的。綁定原理請看下圖:

除此之外,SLF4J 還提供了橋接包,它的意思是指可以把使用某個具體 Log 組件的 API 重定向到 SLF4J 的 API 裏(前提需要排除具體實現包,然後引入橋接包),然後 SLF4J 會根據具體的綁定包輸出內容,從而達到多種日誌實現統一輸出的目的。綁定原理請看下圖:

中間件對日誌的選擇

上面解決了業務開發人員的問題,那麼對於從事中間件的開發者來說呢?日誌依舊是一個痛點。參考一些中間件項目,如 zookeeper 使用的是 log4j ,hibernate-validator 使用的是 jboss-logging,當業務開發人員去集成這些第三方組件時,就會感到頭疼,因爲這些組件的日誌實現很有可能會和當前業務自身的日誌依賴產生衝突。常用的解決方法就是排除某一種日誌實現依賴,然後修改 appender 和 logger 達到日誌隔離。但這並不是一個一勞永逸的方法,因爲每次引入新的 jar 包,你都需要考慮是否有日誌衝突。

那麼市面上是否有成熟的框架來解決這個問題呢?當然是有的,螞蟻金服開源的 SOFABoot 就提供了這樣的功能,底層主要是通過 sofa-common-tools 實現的。那麼 sofa-common-tools 又是個啥呢?借用官網的描述:sofa-common-tools 是 SOFAStack 中間件依賴的一個通用工具包,通過自動感知應用的日誌實現,提供中間件與應用隔離的日誌空間打印能力。

本篇將通過一個案例 demo 先來直觀的體驗下 sofa-common-tools 所能解決的問題,然後再在此基礎上,通過源碼解析瞭解其內部的具體實現原理,以幫助大家更好的認識和了解 sofa-common-tools 這個“小而美”的日誌工具包。

2 日誌隔離實戰

完整項目已經上傳到 https://github.com/masteryourself/study-sofa.git ,工程是 study-sofa-common-tools。

有這樣一個場景:公司的中間件團隊做了一款 middleware-apm 監控系統,並且通過以輸出日誌的方式向監控系統提供基礎數據。由於公司並沒有制定統一的日誌規範,各個業務方所使用的日誌也是千差萬別;如:如訂單系統使用的是 log4j,賬務系統用的 Logback,用戶中心用的是 Log4j2;如果期望 apm 提供的日誌輸出和業務的不衝突,可以獨立的並且完整的兼容業務日誌的不同實現,此時便可以使用 SOFABoot 提供的日誌隔離框架;其可以幫助我們解決日誌實現衝突、日誌文件隔離以及動態調試日誌級別等功能。下面就先來看下 apm 是如何使用 sofa-commons-tools 來實現的。

middleware-apm 項目

新建 middleware-apm 工程,然後執行 mvn clean install 命令,安裝到本地倉庫。具體代碼可以參考 middleware-apm ,下面對一些核心代碼進行簡單的說明和分析。代碼結構如下:

middleware-apm: https://github.com/masteryourself/study-sofa/tree/master/study-sofa-common-tools/middleware-apm

日誌資源文件配置

這裏主要是在 pers.masteryourself.study.sofa.apm.log 目錄下創建 log4j、log4j2、logback 的配置文件。

詳情請參考: https://github.com/masteryourself/study-sofa/tree/master/study-sofa-common-tools/middleware-apm/src/main/resources/pers/masteryourself/study/sofa/apm/log

核心日誌工廠類 -ApmLoggerFactory

ApmLoggerFactory 主要是對外提供獲取 Logger 實例的 API 方法,其作用類似於 slf4j 中的 LoggerFactory 類;對於想使用 SOFABoot 日誌特性的類,只要使用它調用 getLogger 方法獲得的 Logger 實例即可。

LoggerFactory 和 ApmLoggerFactory 的最本質區別在於 ApmLoggerFactory 引入了 LOG_SPACE 的概念。

監控工具類 -Metrics

模擬 APM 對外提供的一個工具類,提供了一個 metrics 埋點的 API ,其內部主要是通過 ApmMetrics 類進行埋點。

監控核心類 -ApmMetrics

ApmMetrics 模擬 APM 監控的一些數據指標、異常信息,如果出錯了就調用 error 方法記錄異常,最後調用 end 方法提交,這裏提交任務只是簡單的打印輸出。

此項目主要是對外提供入口方法用於監控程序的運行情況,項目的日誌會單獨記錄到一個文件夾中,與業務方日誌分開打印,具體效果請配合 user-center 項目一起使用。

user-center 項目

此工程是 SpringBoot 工程,主要是引入 middleware-apm 的 jar 包和 SpringBoot 相關的包。

具體代碼可以參考: https://github.com/masteryourself/study-sofa/tree/master/study-sofa-common-tools/user-center

下面列舉一些核心代碼。代碼結構如下:

資源文件配置

  • application.properites

  • log4j2.xml

log 配置請參考: https://github.com/masteryourself/study-sofa/tree/master/study-sofa-common-tools/user-center/src/main/resources

業務類 -UserService

模擬一段業務程序的運行,如果運行時間超過 500ms 表示程序超時,拋出異常。

測試用例 -UserServiceTest

這裏用的是 SpringBoot 提供的測試註解,在調用真正的業務方法之前,會先用 APM 進行埋點,監控程序運行情況。

運行結果

運行 UserServiceTest 測試用例,我們可以看到 console 控制檯上生成了兩種日誌格式的信息。

對於文件中的日誌呢?我們先把 application.properties 中的配置先註釋,再運行一次測試用例,讓它生成文件日誌。我們去中間件的日誌目錄和業務的日誌目錄,可以看到生成了兩份日誌,日誌隔離效果生效。

3 源碼解析

考慮到直接看源碼可能比較枯燥無聊,這裏準備了一個流程圖,大致分析出了 sofa-common-tools 的整個原理,下面就一些重要的步驟給出解析,如果大家想看源碼註釋部分,可參考我在 github 上的源碼解析工程 ,裏面對部分核心代碼做了註釋。

源碼解析工程: https://github.com/masteryourself/sofa-common-tools

①:Spring 容器啓動,會發布 ApplicationEnvironmentPreparedEvent 事件(Spring 加載配置文件事件),然後會通知 CommonLoggingApplicationListener 監聽器。

②:在這個監聽器中,主要是查找是否存在一個這樣的 key:key 以 sofa.middleware.log. 開頭且 key 不等於 sofa.middleware.log.console 且 key 以 .console 結尾,如果有就先初始化 log,然後 reInitialize。

③:在這裏會首先初始化一個 SpaceInfo ,也即每個 SpaceId 對應一個 SpaceInfo , SpaceInfo 裏有啥呢,有一個 AbstractLoggerSpaceFactory ,它有多個實現(包含 log4j2、logback 等),看到這裏應該就明白了爲啥要求每個中間件組件都有一個唯一的 spaceId 的原因了,日誌隔離就是靠這個 spaceId 來實現的。

④:從名字上看就知道這是一個多種日誌與空間對應管理的類, createILoggerFactory 顧名思義就是創建 AbstractLoggerSpaceFactory ,它實現了 SLF4J 的 ILoggerFactory 接口。如果組件不是以 Spring-Boot 方式啓動初始化的,那麼就會等到手動調用此 API 的時候再去初始化 logger。

⑤:log4j2 判斷:判斷規則是看當前 classloader 能夠加載到 org.apache.logging.slf4j.Log4jLoggerFactory 類,如果可以加載到,再判斷配置文件中是否禁用了 log4j2。logback、log4j、jcl 的判斷與之基本類似,請讀者自行分析。

⑥:這裏加載的規則是讀取 spaceName + /log/log4j2/log-conf.xml,也即我們在項目實戰中定義的特殊位置的配置文件,如果有多個,則會根據 priority 屬性排序。

⑦:初始化 loggerContext,注意它是 log4j2 中的對象,再接下來即是拿到 xml 中的配置,然後去初始化 log 組件,在此不做分析,有興趣的同學可參考 log4j2 官網研究一下。值得一提的是,在這裏 SOFA 團隊也提供了擴展機制 Log4j2FilterGenerator,是通過 spi 來做擴展的,通過它可以添加自定義的 filter。

⑧:在這裏首先會去判斷 logger 是否已經存在,不存在的會調用 newLogger 方法創建,在這個方法裏,實際上就是用之前創建的 loggerContext 對象去創建 logger。

⑨:這裏會根據配置文件中的值判斷是否需要添加 consoleAppender,因爲目前 SOFA 只支持通過 properties 添加一個 ConsoleAppender ,並不能在配置文件中配置很高級的自定義 appender。

至此我們應該瞭解了 sofa-common-tools 的大概原理了,重點主要有如下三個:

  • log-sofa-boot-starter 比 sofa-common-tools 多了個 reInitialize 方法,同時支持通過 Spring 配置文件的方式去設置一些 log 屬性;
  • sofa-common-tools 自動發現日誌實現是有優先級順序的,logback > log4j2 > log4j > jcl,但同時也提供了禁用屬性來打破這種默認規則;
  • sofa-common-tools 提供了一些 SPI 擴展,如 Log4j2FilterGenerator 、 Log4j2ReInitializer 等,具體可見 jar 包中的 META-INF/services 目錄 ;

從 LoggerSpaceFactoryBuilder 類圖分析可知,它有四種 LogFactoryBuilder 實現,所以 sofa-common-tools 並不是重複造輪子,底層還是用市面上常見的日誌組件去實例化 log 對象。

從 AbstractLoggerSpaceFactory 類圖分析可知,它實現了 SLF4J 的 ILoggerFactory 接口,從而替換 SLF4J 的實現。

最後,讓我們來看看 sofa-boot 下各個組件之間的日誌隔離效果吧。

4 總結

通過上文的分析,我們可以知道這款日誌框架的主要作用了:

  • 解決日誌衝突問題;
  • 實現日誌隔離效果;
  • 動態調試日誌級別;
  • 提供了 SPI 擴展,能夠自由擴展組件;
  • 提供啓動參數,通過設置啓動參數可以更改日誌的默認行爲,使用更加靈活;

其實在整個 SOFA 體系中,sofa-common-tools 也是被廣泛使用的,比如 sofa-rpc 中的 RpcLoggerFactory ,sofa-ark 中的 ArkLoggerFactory ,它們都是基於此實現的日誌功能。總而言之,這款產品最主要的還是適用於以中間件的形式對外提供服務,如果大家是從事中間件開發或者有類似的需求,不妨可以試一試這款產品,只需要很小的改動,就可以享受到它獨特的魅力。

本文轉載自公衆號金融級分佈式架構(ID:Antfin_SOFA)。

原文鏈接:

https://mp.weixin.qq.com/s/TUudrkt23iFMMIyv-MmD1g

相關文章