一、匯入
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