【Logback日誌級別】動態調整Logback的日誌級別

羅西施發表於2021-08-03

一、匯入

  Logback作為目前一個比較流行的日誌框架,我們在實際專案經常使用到該框架來幫助我們列印日誌,以便我們可以更快速地獲取業務邏輯執行情況、定位系統問題。

  常用的日誌列印一共有5種級別控制,優先順序情況為:【TRACE】<【DEBUG】<【INFO】<【WARN】<【ERROR】。

  【TRACE】:trace是一種很低的日誌級別,一般不會使用。目前,我只有在SpringBoot的啟動之中,略微發現一些它的影子,表示的就是預設不列印的日誌。

  【DEBUG】:debug是一種除錯程式的日誌級別,一般用於程式開發過程中列印一些除錯日誌、執行資訊,可以比較隨意的使用。

  【INFO】:info是用來輸出程式的一些關鍵資訊,強調業務邏輯的執行過程資訊,不能隨便列印。

  【WARN】:warn是用於輸出一些警告提示資訊,一般是系統進入到一種可恢復的狀態時列印的資訊,警告但不是嚴重錯誤。

  【ERROR】:error是系統已經發生錯誤的事件,比如發生了異常,但是不想影響系統的正常執行。可以列印一些錯誤資訊,提示開發者關注或者定位問題。

  日誌是列印在日誌檔案之中的,如果大量的列印會造成日誌檔案的驟增導致磁碟空間快速增長。但是,排查問題的時候,我們都儘可能希望日誌級別可以足夠的細緻。沒有問題的時候,我們希望日誌檔案可以儘量減少磁碟的佔用。

  所以,如果我們可以做到動態地去控制日誌級別,實現動態列印日誌,那就可以完美解決上訴的需求。

 

二、敲程式碼

  

  1.先來個不同日誌級別的列印類。

  每個列印類都只做一件事,判斷一下當前級別,然後列印一行日誌。(實際上,logback裡面已經幫我們做了判斷,我們在這裡只是為了程式碼規範,減少日誌拼接再列印的消耗,所以提前做了日誌級別判斷)

  TRACE級別:

package cn.lxw.trace;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Title:
 * @ClassName: cn.lxw.trace.TraceLogger.java
 * @Description:
 *
 * @Author: luoxw
 * @Date: 2021/8/3 19:13
 */
public class TraceLogger {
    private static final Logger logger = LoggerFactory.getLogger(TraceLogger.class);

    /**
     * 功能描述: <br>
     * 〈Print trace-level log〉
     * @Param: []
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:12
     */
    public static void printLog(){
        if(logger.isTraceEnabled()) {
            logger.trace("print trace log");
        }
    }

}

  DEBUG級別:

package cn.lxw.debug;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Title:
 * @ClassName: cn.lxw.debug.DebugLogger.java
 * @Description:
 *
 * @Author: luoxw
 * @Date: 2021/8/3 19:12
 */
public class DebugLogger {
    private static final Logger logger = LoggerFactory.getLogger(DebugLogger.class);

    /**
     * 功能描述: <br>
     * 〈Print debug-level log〉
     * @Param: []
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:12
     */
    public static void printLog(){
        if(logger.isDebugEnabled()) {
            logger.debug("print debug log");
        }
    }

}

  INFO級別:

package cn.lxw.info;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Title:
 * @ClassName: cn.lxw.info.InfoLogger.java
 * @Description:
 *
 * @Author: luoxw
 * @Date: 2021/8/3 19:13
 */
public class InfoLogger {
    private static final Logger logger = LoggerFactory.getLogger(InfoLogger.class);

    /**
     * 功能描述: <br>
     * 〈Print info-level log〉
     * @Param: []
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:12
     */
    public static void printLog(){
        if(logger.isInfoEnabled()){
            logger.info("print info log");
        }
    }

}

  WARN級別:

package cn.lxw.warn;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Title:
 * @ClassName: cn.lxw.warn.WarnLogger.java
 * @Description:
 *
 * @Author: luoxw
 * @Date: 2021/8/3 19:13
 */
public class WarnLogger {
    private static final Logger logger = LoggerFactory.getLogger(WarnLogger.class);

    /**
     * 功能描述: <br>
     * 〈Print warn-level log〉
     * @Param: []
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:12
     */
    public static void printLog(){
        if(logger.isWarnEnabled()) {
            logger.warn("print warn log");
        }
    }

}

  ERROR級別:

package cn.lxw.error;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Title:
 * @ClassName: cn.lxw.error.ErrorLogger.java
 * @Description:
 *
 * @Author: luoxw
 * @Date: 2021/8/3 19:13
 */
public class ErrorLogger {
    private static final Logger logger = LoggerFactory.getLogger(ErrorLogger.class);

    /**
     * 功能描述: <br>
     * 〈Print error-level log〉
     * @Param: []
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:12
     */
    public static void printLog(){
        if(logger.isErrorEnabled()) {
            logger.error("print error log");
        }
    }

}

  2.將這些日誌列印封裝到一個方法中。

    /**
     * 功能描述: <br>
     * 〈print log with different logger levels.〉
     * @Param: []
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:11
     */
    private static void printLog() {
        TraceLogger.printLog();
        DebugLogger.printLog();
        InfoLogger.printLog();
        WarnLogger.printLog();
        ErrorLogger.printLog();
    }

  3.寫一個隨機返回日誌級別的方法

    private static Level getRandomLevel(){
        Level[] levelArr = new Level[]{Level.TRACE,Level.DEBUG,Level.INFO,Level.WARN,Level.ERROR};
        int index = (int) (Math.random() * 10) % 6 + 1;
        if(index > levelArr.length - 1){
            index = 0;
        }
        return levelArr[index];
    }

  4.核心修改來了,動態調整日誌級別。這裡主要是獲取日誌的上下文物件,是從ILoggerFactory轉化來的,因為我們的日誌上下文LoggerContext實現了ILoggerFactory介面。這裡我的做法是獲取所有的日誌類,然後遍歷出符合我們包名或者類名的條件,然後遍歷設定對應的日誌級別。

    /**
     * 功能描述: <br>
     * 〈refresh the level setting of the logger context〉
     * @Param: [loggerPackage, loggerLevel]
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:11
     */
    private static void refreshLoggerLevel(String loggerPackage, Level loggerLevel) {
        // #1.get logger context
        ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory();
        List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
        // #2.filter the Logger object
        List<Logger> packageLoggerList = loggerList.stream().filter(a -> a.getName().startsWith(loggerPackage)).collect(Collectors.toList());
        // #3.set level to logger
        for (ch.qos.logback.classic.Logger logger : packageLoggerList) {
            logger.setLevel(loggerLevel);
        }
    }

  5.開始測試。開啟一個定時執行緒池,然後列印日誌,獲取隨機的一個日誌級別,然後重新整理日誌上下文(設定日誌級別)。下一個任務執行時間,繼續重複上訴操作,觀察變化。

    /**
     * 功能描述: <br>
     * 〈dynamic setting of Logback level start here.〉
     * @Param: [args]
     * @Return: {@link Void}
     * @Author: luoxw
     * @Date: 2021/8/3 19:06
     */
    public static void main(String[] args) {
        // you should know that the logger-level priority:
        //      trace < debug < info < warn < error

        // At first,define a scheduled thread.
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            // Secondly, print log.
            printLog();
            // get logger-package and logger-level here(you can fetch the config from database or config-center)
            // Thirdly, get logger level which is configured by you.
            Level randomLevel = getRandomLevel();
            System.out.println("logger level next time is : " + randomLevel);
            // Finally,refresh the logger level which package is LOGGER_PACKAGE
            refreshLoggerLevel(LOGGER_PACKAGE,randomLevel);
        }, 2, 2, TimeUnit.SECONDS);
    }

  6.檢視日誌,符合預期。通過該調整,可以實現我們需要的功能操作。

20:16:04.360 [pool-1-thread-1] DEBUG cn.lxw.debug.DebugLogger - print debug log
20:16:04.364 [pool-1-thread-1] INFO cn.lxw.info.InfoLogger - print info log
20:16:04.366 [pool-1-thread-1] WARN cn.lxw.warn.WarnLogger - print warn log
20:16:04.367 [pool-1-thread-1] ERROR cn.lxw.error.ErrorLogger - print error log
logger level next time is : WARN
20:16:06.137 [pool-1-thread-1] WARN cn.lxw.warn.WarnLogger - print warn log
20:16:06.137 [pool-1-thread-1] ERROR cn.lxw.error.ErrorLogger - print error log
logger level next time is : WARN
20:16:08.138 [pool-1-thread-1] WARN cn.lxw.warn.WarnLogger - print warn log
20:16:08.138 [pool-1-thread-1] ERROR cn.lxw.error.ErrorLogger - print error log
logger level next time is : DEBUG
20:16:10.138 [pool-1-thread-1] DEBUG cn.lxw.debug.DebugLogger - print debug log
20:16:10.138 [pool-1-thread-1] INFO cn.lxw.info.InfoLogger - print info log
20:16:10.138 [pool-1-thread-1] WARN cn.lxw.warn.WarnLogger - print warn log
20:16:10.138 [pool-1-thread-1] ERROR cn.lxw.error.ErrorLogger - print error log
logger level next time is : ERROR
20:16:12.137 [pool-1-thread-1] ERROR cn.lxw.error.ErrorLogger - print error log

 

三、總結

  這個動態日誌列印其實不難,只是網路上的資料太少,需要自己翻閱一些原始碼,找到裡面核心的內容,然後通過一些框架提供的類按照我們的需求進行自定義。原始碼還是要多看的,學會其中思想,從實際出發,解決專案問題。好了,今天分享到這,謝謝大家的觀看!

 

  專案地址:https://github.com/telephone6/java-collection/tree/main/frame/logback

 

相關文章