logger SpringBoot logback slf4j

SpringBoot org.slf4j.Logger設定檔 logback.xml秘密解析

陳瑞泰 John Chen 2021/11/09 09:31:51
323

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 name="com" />

  • <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

 

陳瑞泰 John Chen