Spring按業務模組輸出日誌到不同的檔案

huan1993發表於2022-05-06

一、背景

在我們開發的過程中,可能存在如下情況:
1、有些時候我們需要呼叫第三方的介面,一般情況下,呼叫介面,我們都會記錄請求的入參和響應的。如果我們自己系統的日誌和第三方的日誌混合到一個日誌檔案中,那麼可能查詢日誌就比較麻煩了。那麼我們是否可以將第三方系統的日誌單獨放到另外的檔案中呢?

2、或者有些時候我們系統需要進行資料遷移,如果某條資料遷移失敗了,是否單獨放到一個日誌檔案中比較清晰呢?

二、需求

需求
從上圖中可以看到我們的需求比較簡單

1、系統啟動日誌和 login 模組日誌記錄到 springboot-spring.log 檔案中。
2、第三方業務(QQ)模組的日誌記錄到 springboot-qq.log檔案中。
3、第三方業務(QQ)模組提供了一個login(loginName)方法,方法的入參loginName需要記錄到springboot-qqLoginName.log檔案中,模擬一、背景中提到的資料遷移失敗,記錄失敗的資料到單獨的日誌檔案中。

三、技術實現

1、採用的日誌框架

此處使用logback來完成日誌的記錄,因為SpringBoot應用程式預設的就是採用的logback來記錄日誌。

2、如果實現分模組、分檔案記錄日誌

1、 編寫appender,這個可以簡單的理解日誌需要輸出到哪裡。

比如:

<!-- 此處定義的日誌輸出到控制檯 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %logger{50}#%method:%L -%msg%n</pattern>

    </encoder>
</appender>

<!-- 此處定義日誌輸出到 springboot-qq-日期.第幾個.log 檔案中 -->
<appender name="qqAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <fileNamePattern>logs/springboot-qq-%d{yyyy-MM-dd}-.%i.log</fileNamePattern>
    </rollingPolicy>
   
</appender>

2、如何實現模組輸出日誌

此處就需要我們來配置 logger了。logger的name屬性指定到具體的全包名,然後引用我們上面定義的appender即可。

<!-- 在QQ這個包下的日誌單獨使用 qqAppender 來輸出 -->
<logger name="com.huan.springboot.qq" level="info" additivity="false">
    <appender-ref ref="qqAppender"/>
    <appender-ref ref="stdout"/>
</logger>

配置logger,logger的name為需要單獨生成檔案的那個包的全包名,然後在裡面引用上面定義的appender

3、如果實現將loginName輸出到指定的檔案

其實還是使用 logger 來實現,logger的name需要和 LoggerFactory.getLogger("此處寫具體logger的name的值")
如果實現將loginName輸出到指定的檔案
注意:
此處可能有一個坑,就是可能會丟失類名,那麼我們如何進行解決呢?可以通過MDC來解決。

.... %X{CLASSNAME}#%method:%L -%msg%n
MDC.put("CLASSNAME", QQService.class.getName());
qqLoginName.info("登入使用者:[{}]", loginName);

xml中使用%X{CLASSNAME},在java程式碼中使用MDC存入CLASSNAME的值。

四、程式碼實現

1、編寫xml日誌檔案

1、編寫appender

1、輸出日誌到控制檯

<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %logger{50}#%method:%L -%msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>

2、編寫login模板的日誌

<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/springboot-spring-%d{yyyy-MM-dd}-.%i.log</fileNamePattern>
            <maxHistory>7</maxHistory>
            <maxFileSize>1MB</maxFileSize>
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %logger{50}#%method:%L -%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

3、編寫qq模板的日誌

<appender name="qqAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/springboot-qq-%d{yyyy-MM-dd}-.%i.log</fileNamePattern>
            <maxHistory>7</maxHistory>
            <maxFileSize>1MB</maxFileSize>
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %logger{50}#%method:%L -%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

4、編寫qq模組loginName單獨輸出到檔案的日誌

<appender name="qqLoginNameAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/springboot-qqLoginName-%d{yyyy-MM-dd}-.%i.log</fileNamePattern>
            <maxHistory>7</maxHistory>
            <maxFileSize>1MB</maxFileSize>
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${PID:- }] [%thread] %-5level %X{CLASSNAME}#%method:%L -%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

2、配置日誌輸出到具體位置

1、配置login模組

<root level="INFO">
    <appender-ref ref="stdout"/>
    <appender-ref ref="file"/>
</root>

login模組屬於我們自己的系統模組,此處使用 root標籤來配置。

2、配置qq模組

<!-- 在QQ這個包下的日誌單獨使用 qqAppender 來輸出 -->
<logger name="com.huan.springboot.qq" level="info" additivity="false">
    <appender-ref ref="qqAppender"/>
    <appender-ref ref="stdout"/>
</logger>

此處name的值直接指定到了qq的全包名路徑。

3、配置loginName單獨輸出到檔案

<!-- 將所有的QQ登入名防止在另外的檔案中 -->
<logger name="qqLoginName" level="info" additivity="false">
    <appender-ref ref="qqLoginNameAppender"/>
    <appender-ref ref="stdout"/>
</logger>

2、編寫QQ模組的程式碼

@Component
public class QQService {

    private static final Logger log = LoggerFactory.getLogger(QQService.class);

    // getLogger("qqLoginName") 裡的 qqLoginName 需要和 logback-spring.xml 中 logger的name一致,才會應用
    private static final Logger qqLoginName = LoggerFactory.getLogger("qqLoginName");

    public void login(String loginName) {
        log.info("QQ業務: 使用者:[{}]開始使用QQ來登入系統", loginName);

        MDC.put("CLASSNAME", QQService.class.getName());
        qqLoginName.info("登入使用者:[{}]", loginName);
    }
}

3、編寫login模組的程式碼

@RestController
public class LoginController {

    private static final Logger log = LoggerFactory.getLogger(LoginController.class);

    @Resource
    private QQService qqService;

    @GetMapping("login/{loginName}")
    public String login(@PathVariable("loginName") String loginName) {
        log.info("自己業務:使用者:[{}]進行登入", loginName);
        qqService.login(loginName);
        return "ok";
    }
}

五、執行結果

執行結果
可以看到得到了我們期望的結果。

六、完整程式碼

https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/springboot-logger-split-file

七、一個小知識點

在SpringBoot中,如果我們要覆蓋預設的logback配置,推薦使用logback-spring.xml來配置。
一個小知識點

相關文章