Logback配置檔案這麼寫,還愁不會整理日誌?

Louis_Liu_Oneself發表於2020-07-28

摘要:

1.日誌輸出到檔案並根據LEVEL級別將日誌分類儲存到不同檔案

2.通過非同步輸出日誌減少磁碟IO提高效能

3.非同步輸出日誌的原理

1、配置檔案logback-spring.xml

SpringBoot工程自帶logbackslf4j的依賴,所以重點放在編寫配置檔案上,需要引入什麼依賴,日誌依賴衝突統統都不需要我們管了。

logback框架會預設載入classpath下命名為logback-spring.xmllogback.xml的配置檔案。

如果將所有日誌都儲存在一個檔案中,檔案大小也隨著應用的執行越來越大並且不好排查問題,正確的做法應該是將error日誌和其他日誌分開,並且不同級別的日誌根據時間段進行記錄儲存。

 

配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 專案名稱 -->
    <property name="PROJECT_NAME" value="project-api" />
    <!--定義日誌檔案的儲存地址 勿在 LogBack 的配置中使用相對路徑-->
    <property name="LOG_HOME" value="logs" />

    <!-- 控制檯輸出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">        
        <withJansi>true</withJansi>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 格式化輸出: %d: 日期; %-5level: 級別從左顯示5個字元寬度; %thread: 執行緒名; %logger: 類名; %M: 方法名; %line: 行號; %msg: 日誌訊息; %n: 換行符 -->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] [%logger{50}] [%M] [%line] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- ERROR日誌檔案,記錄錯誤日誌 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${PROJECT_NAME}/error.log</file>
        <!-- 過濾器,只列印ERROR級別的日誌 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日誌檔案輸出的檔名-->
            <FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/%d{yyyy-MM-dd}/error.%i.zip</FileNamePattern>
            <!--日誌檔案保留天數-->
            <MaxHistory>3650</MaxHistory>
            <!--日誌檔案最大的大小-->
            <MaxFileSize>100MB</MaxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 格式化輸出: %d: 日期; %-5level: 級別從左顯示5個字元寬度; %thread: 執行緒名; %logger: 類名; %M: 方法名; %line: 行號; %msg: 日誌訊息; %n: 換行符 -->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] [%logger{50}] [%M] [%line] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- INFO日誌檔案,用於記錄重要日誌資訊 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${PROJECT_NAME}/info.log</file>
        <!-- 過濾器,只列印INFO級別的日誌 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日誌檔案輸出的檔名-->
            <FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/%d{yyyy-MM-dd}/info.%i.zip</FileNamePattern>
            <!--日誌檔案保留天數-->
            <MaxHistory>3650</MaxHistory>
            <!--日誌檔案最大的大小-->
            <MaxFileSize>100MB</MaxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 格式化輸出: %d: 日期; %-5level: 級別從左顯示5個字元寬度; %thread: 執行緒名; %logger: 類名; %M: 方法名; %line: 行號; %msg: 日誌訊息; %n: 換行符 -->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] [%logger{50}] [%M] [%line] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <!-- 列印的SQL日誌檔案,用於執行的SQL語句和引數資訊 -->
    <appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${PROJECT_NAME}/sql.log</file>
           <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
               <!--日誌檔案輸出的檔名-->
               <FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/%d{yyyy-MM-dd}/sql.%i.zip</FileNamePattern>
               <!--日誌檔案保留天數-->
              <MaxHistory>3650</MaxHistory>
               <!--日誌檔案最大的大小-->
               <MaxFileSize>100MB</MaxFileSize>
           </rollingPolicy>
           <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 格式化輸出: %d: 日期; %-5level: 級別從左顯示5個字元寬度; %thread: 執行緒名; %logger: 類名; %M: 方法名; %line: 行號; %msg: 日誌訊息; %n: 換行符 -->
               <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] [%logger{50}] [%M] [%line] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- API請求被訪問的日誌檔案,記錄請求的URL和攜帶的引數 -->
    <appender name="REQUEST_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${PROJECT_NAME}/request.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日誌檔案輸出的檔名-->
               <FileNamePattern>${LOG_HOME}/${PROJECT_NAME}/%d{yyyy-MM-dd}/request.%i.zip</FileNamePattern>
               <!--日誌檔案保留天數-->
              <MaxHistory>3650</MaxHistory>
               <!--日誌檔案最大的大小-->
               <MaxFileSize>100MB</MaxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
               <!-- 格式化輸出: %d: 日期; %-5level: 級別從左顯示5個字元寬度; %thread: 執行緒名; %logger: 類名; %M: 方法名; %line: 行號; %msg: 日誌訊息; %n: 換行符 -->
               <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] [%logger{50}] [%M] [%line]- %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 非同步輸出INFO_FILE -->
    <appender name="ASYNC_INFO_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="INFO_FILE"/>
    </appender>

    <!-- 非同步輸出ERROR_FILE -->
    <appender name="ASYNC_ERROR_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="ERROR_FILE"/>
    </appender>

    <!-- 非同步輸出SQL_FILE -->
    <appender name="ASYNC_SQL_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="SQL_FILE"/>
    </appender>

    <!-- 非同步輸出REQUEST_FILE -->
    <appender name="ASYNC_REQUEST_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="REQUEST_FILE"/>
    </appender>

    <!-- 輸出error資訊到檔案-->
    <logger name="error" additivity="true">
        <appender-ref ref="ERROR_FILE"/>
    </logger>
    
    <!-- 輸出info資訊到檔案-->
    <logger name="info" additivity="true">
        <appender-ref ref="INFO_FILE"/>
    </logger>
    
    <!-- 輸出request資訊到檔案-->
    <logger name="request" level="INFO" additivity="false">
        <appender-ref ref="REQUEST_FILE" />
    </logger>

    <!-- 輸出SQL到控制檯和檔案-->
    <logger name="org.hibernate.SQL" additivity="false">
        <level value="DEBUG" />
        <appender-ref ref="SQL_FILE" />
    </logger>

   <!-- 輸出SQL的引數到控制檯和檔案-->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" additivity="false" level="TRACE">
        <level value="TRACE" />
        <appender-ref ref="SQL_FILE" />
    </logger>

    <!-- 開發環境下的日誌配置 -->
    <springProfile name="dev">
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="INFO_FILE" />
        </root>
    </springProfile>

    <!-- 測試環境下的日誌配置 -->
    <springProfile name="test">
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="INFO_FILE" />
        </root>
    </springProfile>

    <!-- 生產環境下的日誌配置 -->
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="INFO_FILE" />
        </root>
    </springProfile>
</configuration>

 

標籤說明

<root>標籤,必填標籤,用來指定最基礎的日誌輸出級別
<appender-ref>標籤,新增append
<append>標籤,通過使用該標籤指定日誌的收集策略
<filter>標籤,通過使用該標籤指定過濾策略
<level>標籤指定過濾的型別
<encoder>標籤,使用該標籤下的<pattern>標籤指定日誌輸出格式
<rollingPolicy>標籤指定收集策略,比如基於時間進行收集
<fileNamePattern>標籤指定生成日誌儲存地址,通過這樣配置已經實現了分類分日期收集日誌的目標了

name屬性指定appender命名
class屬性指定輸出策略,通常有兩種,控制檯輸出檔案輸出,檔案輸出就是將日誌進行一個持久化
ConsoleAppender將日誌輸出到控制檯

 

部分截圖展示:

目錄:

 

error.log

 

info.log

Logback配置檔案這麼寫,還愁不會整理日誌?

 

sql.log

request.log

Logback配置檔案這麼寫,還愁不會整理日誌?

2、logback 高階特性非同步輸出日誌

如果不配置非同步輸出規則,那麼預設日誌配置方式是基於同步的,每次日誌輸出到檔案都會進行一次磁碟IO

採用非同步寫日誌的方式而不讓此次寫日誌發生磁碟IO,阻塞執行緒從而造成不必要的效能損耗。

非同步輸出日誌的方式很簡單,新增一個基於非同步寫日誌的appender,並指向原先配置的appender即可。

 

非同步輸出配置:

<!-- 非同步輸出INFO_FILE -->
    <appender name="ASYNC_INFO_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="INFO_FILE"/>
    </appender>

    <!-- 非同步輸出ERROR_FILE -->
    <appender name="ASYNC_ERROR_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="ERROR_FILE"/>
    </appender>

    <!-- 非同步輸出SQL_FILE -->
    <appender name="ASYNC_SQL_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="SQL_FILE"/>
    </appender>

    <!-- 非同步輸出REQUEST_FILE -->
    <appender name="ASYNC_REQUEST_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 更改預設的佇列的深度,該值會影響效能.預設值為256 -->
        <queueSize>256</queueSize>
        <!-- 預設情況下,當阻塞佇列的剩餘容量為20%時,它將丟棄TRACE,DEBUG和INFO級別的事件,僅保留WARN和ERROR級別的事件。要保留所有事件,請將discardingThreshold設定為0。 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 新增附加的appender,使用前面定義的name,最多隻能新增一個 -->
        <appender-ref ref="REQUEST_FILE"/>
    </appender>

 

引數配置:

引數名稱     引數型別     建議值     說明
queueSize int queueSize計算

example:假設IO影響30s,日誌和qps比例是1:1,單容器壓測值1500 qps則可以推算出queue size的值,queueSize的設定公式:30 *1500=45000。
阻塞佇列的最大容量。預設情況下,queueSize設定為256。
discardingThreshold int

使用預設值20,或者設定大於0。

如果設定discardingThreshold=0,表示queue 滿了,不丟棄,block執行緒。

預設情況下,當阻塞佇列剩餘20%的容量時,它將丟棄級別跟蹤、除錯和資訊事件,只保留級別警告和錯誤事件。要保留所有事件,請將discardingThreshold設定為0。
neverBlock boolean true 如果為false(預設值),則追加程式將阻止追加到完整佇列,而不是丟失訊息。設定為true時,附加程式只會丟棄訊息,不會阻止您的應用程式。

 

 

 

 

 

 

 




官方文件連結:http://logback.qos.ch/manual/appenders.html#AsyncAppender

3、非同步日誌輸出原理

非同步輸出日誌中最關鍵的就是配置檔案中ch.qos.logback.classic包下AsyncAppenderBase類中的append方法,檢視該方法的原始碼:

@Override
protected void append(E eventObject) {
    if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) {
        return;
    }
    preprocess(eventObject);
    put(eventObject);
}

通過佇列情況判斷是否需要丟棄日誌,不丟棄的話將它放到阻塞佇列中,通過檢視程式碼,這個阻塞佇列為queueSize,預設大小為256,可以通過配置檔案進行修改。

/**
 * The default buffer size.
 */
public static final int DEFAULT_QUEUE_SIZE = 256;
int queueSize = DEFAULT_QUEUE_SIZE;

Logger.info(...)append(...)就結束了,只做了將日誌塞入到阻塞佇列的事,然後繼續執行Logger.info(...)下面的語句了。在AsyncAppenderBase類中定義了一個Worker執行緒,run()方法中的程式碼如下:

Worker worker = new Worker();
class Worker extends Thread {

    public void run() {
        AsyncAppenderBase<E> parent = AsyncAppenderBase.this;
        AppenderAttachableImpl<E> aai = parent.aai;

        // loop while the parent is started
        while (parent.isStarted()) {
            try {
                E e = parent.blockingQueue.take();
                aai.appendLoopOnAppenders(e);
            } catch (InterruptedException ie) {
                break;
            }
        }

        addInfo("Worker thread will flush remaining events before exiting. ");

        for (E e : parent.blockingQueue) {
            aai.appendLoopOnAppenders(e);
            parent.blockingQueue.remove(e);
        }

        aai.detachAndStopAllAppenders();
    }
}

從阻塞佇列中取出一個日誌,並呼叫AppenderAttachableImpl類中的appendLoopOnAppenders方法維護一個Append列表。

 

最主要的兩個方法就是encodewrite方法,前一個法方會根據配置檔案中encode指定的方式轉化為位元組碼,後一個方法將轉化成的位元組碼寫入到檔案中去。

所以寫檔案是通過新起一個執行緒去完成的,主執行緒將日誌放到阻塞佇列中,然後又去執行其他任務。

 

相關文章