SpringBoot2.7 霸王硬上弓 Logback1.3 → 不甜但解渴

青石路發表於2024-07-30

開心一刻

一大早,她就發訊息質問我
她:你給我老實交代,昨晚去哪鬼混了?
我:沒有,就哥幾個喝了點酒
她:那我給你打了那麼多影片,為什麼不接?
我:不太方便呀
她:我不信,和你哥們兒喝酒有啥不方便接影片的?
她:你肯定有別的女人了!
我:你老公就坐在我旁邊,我敢接?

不敢接

前情回顧

SpringBoot2.7還是任性的,就是不支援Logback1.3,你能奈他何 講了很多,總結下來就兩點

  1. SpringBoot 2.7.x 預設依賴 Logback 1.2.x,不支援 Logback 1.3.x

    如果強行將 Logback 升級到 1.3.x,啟動會報異常

    Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
    	at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:304)
    	at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:118)
    	at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:238)
    	at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:220)
    	at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:178)
    	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:171)
    	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:145)
    	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:133)
    	at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:79)
    	at org.springframework.boot.SpringApplicationRunListeners.lambda$starting$0(SpringApplicationRunListeners.java:56)
    	at java.util.ArrayList.forEach(ArrayList.java:1249)
    	at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
    	at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:56)
    	at org.springframework.boot.SpringApplication.run(SpringApplication.java:299)
    	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300)
    	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1289)
    	at com.qsl.Application.main(Application.java:15)
    Caused by: java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder
    	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    	... 17 more
    

    原因也分析過了

    spring-boot-2.7.18 依賴 org.slf4j.impl.StaticLoggerBinder,而 logback 1.3.x 沒有該類

  2. SpringBoot 2.7.x 支援 Logback 1.3.x 也不是沒辦法,但有一些限制,同時也存在一些未知的風險

    關於未知的風險,相信大家都能理解,為什麼了,這就好比從 JDK8 升級到 JDK 11,你們為什麼不敢升,一個道理,因為大版本的升級,變動點往往比較多,甚至會移除掉低版本的一些內容,編譯期報錯還算直觀的(我們可以根據報錯調整程式碼),如果是執行期報錯那就頭疼了,上了生產就算事故了,這鍋你敢背嗎?
    所以大版本的升級,意味著我們不但要修復編譯期的錯,還要進行全方位的測試,儘可能的覆蓋所有場景,以排除執行期可能存在的任何異常。業務簡單還好,如果業務非常龐大,這個全量測試是要花大量時間的,不僅開發會口吐芬芳,測試也會 mmp
    Upgrade to SLF4J 2.0 and Logback 1.4 進行了一些討論,wilkinsona(Spring Boot 目前 Contributor 榜一)就提到了一些風險點

    風險點1

    裡面討論了很多,Logback 的 Contributor 榜一大哥 ceki 也在裡面進行了很多說明與答疑,感興趣的可以詳細看看
    總之就是:透過調整配置,SpringBoot 2.7.x 可以支援 Logback 1.3.x,但風險需要我們自己承擔

換個角度想想,我們應該是能理解 Spring Boot 官方的

  1. Logback 不是那麼熟,只能透過 Logback 官方說明知道變動點(能保證事無鉅細列全了?),若變動點太多,不可能每個點都去核實
  2. Spring Boot 那麼龐大,整合了那麼多功能,怕是榜一大哥也不能熟記所有細節(我們敢保證對我們負責的專案的所有細節都瞭如指掌嗎),所以也沒法評估升級到 Logback 1.3.x 會有哪些點受影響

所以求穩,Spring Boot 2.x.x 不打算整合 Logback 1.3.x
但是,如果我們也任性一回,非要強扭這個瓜,Spring Boot 是不是也不能奈我們何?

不甜但解渴

霸王硬上弓

springboot2.7整合Logback1.3

參考這個,我們來配置下

  1. 關閉 Spring BootLoggingSystem

    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            System.setProperty("org.springframework.boot.logging.LoggingSystem", "none");
            SpringApplication.run(Application.class, args);
        }
    }
    
  2. 配置檔案用 logback.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <property name="LOG_FILE" value="/logs/spring-boot-2_7_18.log"/>
        <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%t|%line|%-40.40logger{39}:%msg%n"/>
    
        <!-- 按照每天生成日誌檔案-->
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${FILE_LOG_PATTERN}</pattern>
            </encoder>
            <file>${LOG_FILE}</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.zip</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
        </appender>
    
        <!-- 控制檯輸出 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>${FILE_LOG_PATTERN}}</pattern>
            </encoder>
        </appender>
    
        <root level="${loglevel:-INFO}">
            <appender-ref ref="STDOUT" />
            <appender-ref ref="FILE" />
        </root>
    </configuration>
    

啟動確實正常了,我們加點簡單的業務日誌,發現日誌也輸出正常

2024-07-26 16:46:48.609|INFO|http-nio-8080-exec-1|525|o.s.web.servlet.DispatcherServlet       :Initializing Servlet 'dispatcherServlet'
2024-07-26 16:46:48.610|INFO|http-nio-8080-exec-1|547|o.s.web.servlet.DispatcherServlet       :Completed initialization in 0 ms
2024-07-26 16:46:48.632|INFO|http-nio-8080-exec-1|23|com.qsl.web.TestWeb                     :hello介面入參:青石路
2024-07-26 16:46:50.033|INFO|http-nio-8080-exec-3|23|com.qsl.web.TestWeb                     :hello介面入參:青石路
2024-07-26 16:46:50.612|INFO|http-nio-8080-exec-4|23|com.qsl.web.TestWeb                     :hello介面入參:青石路
2024-07-26 16:46:51.150|INFO|http-nio-8080-exec-5|23|com.qsl.web.TestWeb                     :hello介面入參:青石路
2024-07-26 16:46:51.698|INFO|http-nio-8080-exec-6|23|com.qsl.web.TestWeb                     :hello介面入參:青石路
2024-07-26 16:46:52.203|INFO|http-nio-8080-exec-7|23|com.qsl.web.TestWeb                     :hello介面入參:青石路

日誌檔案寫入也正常

日誌寫入

這不僅解渴,還很甜呀

解渴還甜

但不要甜的太早,這僅僅只是一個 demospring-boot-2_7_18,沒有業務程式碼,簡單的不能再簡單了,你們要是以此來判斷甜與不甜,那就大錯特錯了;應用到專案中,不但要保證能夠正常啟動,還要保證已有的所有業務能夠正常執行,至於計劃中的業務,那就將來再說,誰知道明天和意外哪個先來,認真過好當下!
初步嘗試,是可行的,所以你們大膽的去試吧,但要做好全方位的業務測試

幹就完了

wilkinsona 提到了,關閉 Spring BootLoggingSystem 後,用的是 Logback 的預設配置,配置檔案必須是 logback.xml 而不能是 logback-spring.xml;雖然榜一大哥的話很權威,但我們主打一個任性,就想來試試 logback-spring.xml,會有什麼樣的結果,直接將 logback.xml 改名成 logback-spring.xml,能啟動起來,但有一堆 debug 日誌,重點是

日誌沒有寫入檔案

wilkinsona 誠不欺我!

原理分析

關閉了 Spring BootLoggingSystem 後,日誌相關的全權交給 Logback,而關於 Logback 的配置檔案載入,我是寫過一篇詳解的:從原始碼來理解slf4j的繫結,以及logback對配置檔案的載入,直接跳到總結部分,有這麼一段

編譯期間,完成slf4j的繫結以及logback配置檔案的載入。slf4j會在classpath中尋找org/slf4j/impl/StaticLoggerBinder.class(會在具體的日誌框架如log4j、logback等中存在),找到並完成繫結;同時,logback也會在classpath中尋找配置檔案,先找logback.configurationFile、沒有則找logback.groovy,若logback.groovy也沒有,則找logback-test.xml,若logback-test.xml還是沒有,則找logback.xml,若連logback.xml也沒有,那麼說明沒有配置logback的配置檔案,那麼logback則會啟用預設的配置(日誌資訊只會列印在控制檯)

雖說 Logback1.1.17,而不是 1.3.14,但對配置檔案的載入應該是沒變的

大家注意看我的措辭:應該,這樣即使變了,你們也不能說我,因為我說的是應該

保險起見,你們應該去看下 1.3.14 的原始碼!

這也是為什麼配置檔案是 logback.xml 的時候,日誌能正常寫入檔案,而是 logback-spring.xml 時候,日誌不能寫入日誌檔案的原因,因為 Logback 不認 logback-spring.xmlSpring Boot 才認!
至於 Spring Boot LoggingSystem 嘛,等我掌握了再來和你們聊,一定要等我喲

等我呀

總結

Spring Boot 2.x.x 預設依賴 Logback 1.2.x,不支援 Logback 1.3.x,但是透過設定

System.setProperty("org.springframework.boot.logging.LoggingSystem", "none");

啟動時不報錯的,再結合 logback.xml,日誌是能夠正常寫入日誌檔案的;但是保險起見,還是不推薦升級到 Logback 1.3.x

能不動就不要動,改好沒績效,改出問題要背鍋,吃力不討好,又不是不能跑

如果一定要升級,那就做好全量測試,把所有業務場景都覆蓋到

相關文章