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