SpringBoot org.slf4j.Logger設定檔 logback.xml秘密解析
一般我們在開發Java應用程式時都會在 console 列印出一些訊息來做為 debug 或是記錄之用,實現它的方法也很多種,其中最簡單的方式,就是System.out.println(“bala….bala….”)或是System.err.println(“bala….bala….”) 直接粗爆的在console列印訊息了。然而在Java宇宙中SpringBoot做為一個神級一般的框架它本身就有提供了Logger實作,所以我們不必再花費任何心思直接用就對啦!
如何開始一個Hello Logger
藉由 https://start.spring.io/ 來幫助我們產生一個快速的SpringBoot專案吧,設定檔如下圖所示:
下一步是把它Import 到開發工具中吧,本文中採用Eclipse,然後什麼程式都沒有寫直接跑~~跑~~跑~~起來!!你就可以得到下圖的console資料。
到這步後讓我想想看和專案在開發時有什麼不一樣,嗯~~一樣專案上線時不是都會有一份文字檔的log嗎?通常都是以每日一個單位例如:20210901-myLogger.log之類的命名,以我們就先來產生這樣的一個範例吧!但之後還會有許多進階的說明,所以這裡就直接提供一份完整的設定檔給大家使用,若沒有設定檔,則SpringBoot則以最簡單的default 值提供,設定檔範 logback.xml 例如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds"> <property name="Charset" value="UTF-8" /> <property name="FileName" value="myLogger" /> <property name="LogsLocation" value="logs" /> <property name="Format1" value="%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}.%M\\(%F:%L\\)] %msg%n" /> <!-- appender --> <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${Format1}</pattern> <charset>${Charset}</charset> </encoder> </appender> <appender name="fileout" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LogsLocation}/${FileName}.%d{yyyyMMdd}.%i.log</fileNamePattern> <maxHistory>30</maxHistory> <maxFileSize>256MB</maxFileSize> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${Format1}</pattern> <charset>${Charset}</charset> </encoder> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>100MB</MaxFileSize> </triggeringPolicy> </appender> <!-- logger --> <root level="info"> <appender-ref ref="fileout" /> <appender-ref ref="stdout" /> </root> </configuration> |
把這個檔案放置在resource 目錄下,放置完成如下圖:
然後再次執行console可以得到下圖結果:
順便再進入eclipse專案目錄下的 logs 目錄,你可以看到有一個 myLogger.20210912.0.log 的檔案,它的內容與 console 所呈現的十分相似,那這就要來解釋為什麼是這樣的結果了。
以下使用 xml 格式講解:
1. 所有的組態設定都要放在 <configuration/> 中使用
2. <property/> 相當於定義常數,屬於key-value 架構,後續文中可以使用${xxxx} 來取用常數值
3. <appender/> Log輸出器,或許我翻譯的不好,但它的作用個人感覺它是專門指輸出目的地的設定,甚至它還可以實作,例如:輸出到網路等。
4. <root/> 它其實是 <logger/> 只是它層級比高所以又稱為"根logger"
5. <logger/> log記錄器,可以為它指定收集到的 log 輸出到哪一個 <appender/>
以下說明<appender/>標籤:
常用的Appender有以下幾種:
1.ch.qos.logback.core.ConsoleAppender(控制台輸出,其實就是System.out和System.err)
2.ch.qos.logback.core.FileAppender(持續輸出的日誌檔案)
3.ch.qos.logback.core.rolling.RollingFileAppender(按照每天生成日誌文件 / 超出固定大小後壓縮檔案)
其中ConsoleAppender 及 RollingFileAppender 算是實戰上最實用的二種。
說明<appender/>段落:
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${Format1}</pattern> <charset>${Charset}</charset> </encoder> </appender> |
它指出了專門處理 System.out.print 的串流資料,收到串流資料轉換格式為文字時採用${Charset} = UTF-8 格式,而輸出字串同時還會額外加增一些prefix文字,它的格式由${Format1} 定義,如果大家和我一樣使用 eclipse 執行的話這個輸出目標會出現在Console中,Format1格式與輸出結果的對照圖如下所示:
說明第二個<appender/>段落:
<appender name="fileout" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LogsLocation}/${FileName}.%d{yyyyMMdd}.%i.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${Format1}</pattern> <charset>${Charset}</charset> </encoder> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>100MB</MaxFileSize> </triggeringPolicy> </appender> |
這段的class說明了它會滾動式輸出到檔案,這個類別的屬性比較多,照例直接從範例來看,rollingPolicy這屬性中設定了 file name 的樣式,保留天數為30天,檔案最大為 100MB
說明<logger/>與<root/>段落:
<root level="info"> <appender-ref ref="fileout" /> <appender-ref ref="stdout" /> </root> |
<logger>用來設定某一個包或者具體某一個class的日誌輸出級別、以及指定<appender>。 <logger>可以包含零個或者多個<appender-ref>元素,識別這個appender將會添加到這個logger。 <logger>僅有一個name屬性、一個可選的level屬性和一個可選的additivity屬性:
-
name:用來指定受此logger約束的某一個包或者具體的某一個class
-
level:用來設定輸出級別,五個常用輸出級別從低至高依次為TRACE、DEBUG、INFO、WARN、ERROR,如果未設定此級別,那麼當前logger會繼承上級的級別
-
additivity:是否向上級logger傳遞輸出信息,default為true,某個 Logger 的 additivity=false,表示 log 只打印到本 logger 中,而不再傳入 root 的 Logger.appender
<root>也是<logger>元素,但是它是根logger,只有一個level屬性,因為它的name就是ROOT。
插入一個<logger/>看看:
-
<logger>中沒有指定level,即繼承父級的level,<logger>的父級為<root>,那麼level=debug
-
沒有配置additivity,那麼additivity=true,表示此<logger>會輸出資料給<root>
-
沒有配置<appender-ref>,表示此<logger>不會輸出任何訊息
<logger name="com.tpisoftware" level="warn" additivity="false"> <appender-ref ref="stdout" /> </logger> |
-
LoggerFactory.getLogger(Object.class),首先找到name="com.tpisoftware"這個<logger>,將日誌級別大於等於warn的使用"stdout"這個<appender>印出來
-
name="com.tpisoftware"這個<logger>有設定additivity,輸出訊息不會向上傳遞,傳遞給父級name="com"這個<logger>
-
com.tpisoftware 內的訊息只會輸出至 stdout這個<logger>
設計一個自己的appender:
<!-- MyErrStreamAppender --> <appender name="MyErr" class="com.tpisoftware.myLoggger.MyErrStreamAppender"> <encoder> <pattern>[%thread] %-5level %logger{0} - %n%msg%n</pattern> </encoder> </appender> <!-- logger --> <root level="info"> <appender-ref ref="fileout" /> <appender-ref ref="stdout" /> <!-- MyErrStreamAppender --> <appender-ref ref="MyErr" /> </root> |
設計一個自己定義的 appender 後,記得要把它加到 <root> 或者 <logger> 不然也不會有作動哦!自定義的appender套用方式看起很簡單,重點是程式怎麼呢?
MyErrStreamAppender.java
本範例說明由 logger接收到的字串 “Hello World” 後,經由 logback appender 處理轉出到不同的OutputStream,本列中的OutputStream共有三類,分別為System.out / System.err / File,您可以在 eclipse Console中看到 System.out / System.err,另外由 ./logs 下找到一個檔案 myLogger.20211014.0.log 的檔案,本範例執行日期為 2021 / 10 /14,以下程式碼供參考。 |
import java.io.OutputStream; import ch.qos.logback.core.util.EnvUtil; import ch.qos.logback.core.util.OptionHelper; import ch.qos.logback.core.OutputStreamAppender; import ch.qos.logback.core.joran.spi.ConsoleTarget; public class MyErrStreamAppender<E> extends OutputStreamAppender<E> { protected boolean withJansi = false; // 默認的Target Stream實際上指向 System.out protected ConsoleTarget target = ConsoleTarget.SystemErr; @Override public void start() { // 設置 super 的 Target Stream OutputStream targetStream = target.getStream(); // enable jansi only on Windows and only if withJansi set to true if (EnvUtil.isWindows() && withJansi) { targetStream = getTargetStreamForWindows(targetStream); } // 新的 OutputStream 將會生效, 舊的會失效 setOutputStream(targetStream); super.start(); } private OutputStream getTargetStreamForWindows(OutputStream targetStream) { try { addInfo("Enabling JANSI WindowsAnsiOutputStream for the console."); Object windowsAnsiOutputStream = OptionHelper.instantiateByClassNameAndParameter(WindowsAnsiOutputStream_CLASS_NAME, Object.class, context, OutputStream.class, targetStream); return (OutputStream) windowsAnsiOutputStream; } catch (Exception e) { addWarn("Failed to create WindowsAnsiOutputStream. Falling back on the default stream.", e); } return targetStream; } private final static String WindowsAnsiOutputStream_CLASS_NAME = "org.fusesource.jansi.WindowsAnsiOutputStream"; } |
logback.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds"> <property name="Charset" value="UTF-8" /> <property name="FileName" value="myLogger" /> <property name="LogsLocation" value="logs" /> <property name="Format1" value="%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}.%M\\(%F:%L\\)] %n%msg%n" /> <!-- Console appender --> <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${Format1}</pattern> <charset>${Charset}</charset> </encoder> </appender> <appender name="fileout" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LogsLocation}/${FileName}.%d{yyyyMMdd}.%i.log</fileNamePattern> <maxHistory>30</maxHistory> <maxFileSize>256MB</maxFileSize> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${Format1}</pattern> <charset>${Charset}</charset> </encoder> </appender> <!-- MyErrStreamAppender --> <appender name="MyErr" class="com.tpisoftware.myLoggger.MyErrStreamAppender"> <encoder> <pattern>[%thread] %-5level %logger{0} - %n%msg%n</pattern> </encoder> </appender> <!-- logger --> <root level="info"> <appender-ref ref="fileout" /> <appender-ref ref="stdout" /> <!-- MyErrStreamAppender --> <appender-ref ref="MyErr" /> </root> <logger name="com.tpisoftware" level="debug" additivity="true"/> <!-- <logger name="jdbc.sqlonly" level="WARN"/> <logger name="jdbc.sqltiming" level="INFO"/> <logger name="jdbc.resultsettable" level="INFO"/> <logger name="jdbc.resultset" level="WARN"/> <logger name="jdbc.connection" level="WARN"/> <logger name="jdbc.audit" level="WARN"/> --> </configuration> |
執行結果:
參考資料:
https://matthung0807.blogspot.com/2018/08/java-slf4j-log4j-2.html
https://www.itread01.com/content/1547514018.html
http://logback.qos.ch/manual/appenders.html
https://blog.csdn.net/Peelarmy/article/details/109225138