? 動態創建: 無需配置文件,通過代碼動態創建logback日志對象
? Class對象支持: 使用LogUtil.getLogger(MyClass.class)的方式獲取日志
? 日期格式文件: 自動生成info.%d{yyyy-MM-dd}.log格式的日志文件
? 文件數量管理: 只保留最近3個文件,自動刪除歷史文件
? 單例保證: 相同類名和目錄的日志對象保證是同一個實例
? 啟動時清理: 每次啟動程序時自動清理超過保留數量的歷史文件
? 控制臺輸出控制: 通過全局變量控制是否啟用控制臺輸出
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;import org.slf4j.LoggerFactory;import com.staryea.stream.runner.MainRunner;import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;/*** 動態日志工具類* ? 動態創建: 無需配置文件,通過代碼動態創建logback日志對象* ? Class對象支持: 使用LogUtil.getLogger(MyClass.class)的方式獲取日志* ? 日期格式文件: 自動生成info.%d{yyyy-MM-dd}.log格式的日志文件* ? 文件數量管理: 只保留最近3個文件,自動刪除歷史文件* ? 單例保證: 相同類名和目錄的日志對象保證是同一個實例* ? 啟動時清理: 每次啟動程序時自動清理超過保留數量的歷史文件* ? 控制臺輸出控制: 通過全局變量控制是否啟用控制臺輸出*/
public class LogUtil {// 緩存日志對象,key為類名private static final ConcurrentHashMap<String, Logger> loggerCache = new ConcurrentHashMap<>();// 日志格式private static final String LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{200}:%L - %msg%n";// 默認日志目錄private static final String DEFAULT_LOG_DIR = "logs";/*** 獲取日志對象* @param clazz 類對象* @return Logger對象*/public static Logger getLogger(Class<?> clazz) {return getLogger(clazz, DEFAULT_LOG_DIR);}/*** 獲取日志對象* @param clazz 類對象* @param logDir 日志文件目錄* @return Logger對象*/public static Logger getLogger(Class<?> clazz, String logDir) {String className = clazz.getName();String key = className + "_" + logDir;return loggerCache.computeIfAbsent(key, k -> createLogger(className, logDir));}/*** 創建日志對象* @param className 類名* @param logDir 日志文件目錄* @return Logger對象*/private static Logger createLogger(String className, String logDir) {System.out.println("開始創建日志對象,類名: " + className + ", 目錄: " + logDir);// 確保目錄存在File dir = new File(logDir);if (!dir.exists()) {boolean created = dir.mkdirs();System.out.println("創建目錄結果: " + created);if (!created) {throw new RuntimeException("無法創建日志目錄: " + logDir);}}System.out.println("目錄是否存在: " + dir.exists());System.out.println("目錄是否可寫: " + dir.canWrite());// 獲取LoggerContextLoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();// 創建Logger,使用類名作為Logger名稱。這個有BUG:Logback的LoggerContext對于相同類名總是返回同一個Logger實例,導致后續配置覆蓋了之前的配置(相同類名時路徑被覆蓋)。解決方案是使用包含目錄信息的唯一Logger名稱。
// Logger logger = loggerContext.getLogger(className);// 使用類名 + 目錄作為Logger名稱,確保唯一性String loggerName = className + "_" + logDir.hashCode();Logger logger = loggerContext.getLogger(loggerName);logger.setAdditive(false); // 不繼承父Logger的Appender// 清除已有的Appenderlogger.detachAndStopAllAppenders();// 根據全局開關決定是否添加控制臺輸出if (MainRunner.isTest) {ConsoleAppender<ILoggingEvent> consoleAppender = createConsoleAppender(loggerContext);logger.addAppender(consoleAppender);System.out.println("控制臺Appender添加成功: " + consoleAppender.isStarted());} else {System.out.println("控制臺輸出已禁用,跳過控制臺Appender添加");}// 添加文件輸出(始終存在)RollingFileAppender<ILoggingEvent> fileAppender = createFileAppender(loggerContext, logDir);logger.addAppender(fileAppender);System.out.println("文件Appender添加成功: " + fileAppender.isStarted());// 設置日志級別logger.setLevel(Level.INFO);System.out.println("Logger創建完成,名稱: " + className);System.out.println("Logger級別: " + logger.getLevel());System.out.println("Appender數量: " + logger.iteratorForAppenders().hasNext());return logger;}/*** 創建控制臺Appender*/private static ConsoleAppender<ILoggingEvent> createConsoleAppender(LoggerContext loggerContext) {ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();consoleAppender.setContext(loggerContext);consoleAppender.setName("console");PatternLayoutEncoder encoder = new PatternLayoutEncoder();encoder.setContext(loggerContext);encoder.setPattern(LOG_PATTERN);encoder.start();consoleAppender.setEncoder(encoder);consoleAppender.start();return consoleAppender;}/*** 創建文件Appender*/private static RollingFileAppender<ILoggingEvent> createFileAppender(LoggerContext loggerContext, String logDir) {RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<>();fileAppender.setContext(loggerContext);fileAppender.setName("file");// 創建編碼器PatternLayoutEncoder encoder = new PatternLayoutEncoder();encoder.setContext(loggerContext);encoder.setPattern(LOG_PATTERN);encoder.start();fileAppender.setEncoder(encoder);// 創建滾動策略 - 只按時間輪轉TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();rollingPolicy.setContext(loggerContext);rollingPolicy.setParent(fileAppender);// 設置滾動文件路徑模式 - 固定為info.%d{yyyy-MM-dd}.logString rollingFile = logDir + File.separator + "info.%d{yyyy-MM-dd}.log";rollingPolicy.setFileNamePattern(rollingFile);// 設置保留文件數量(只保留3個文件)rollingPolicy.setMaxHistory(3);// 啟動時清理歷史文件rollingPolicy.setCleanHistoryOnStart(true);rollingPolicy.start();fileAppender.setRollingPolicy(rollingPolicy);// 設置立即刷新fileAppender.setImmediateFlush(true);fileAppender.start();System.out.println("文件Appender啟動狀態: " + fileAppender.isStarted());System.out.println("文件Appender名稱: " + fileAppender.getName());System.out.println("滾動文件模式: " + rollingFile);System.out.println("保留文件數量: " + rollingPolicy.getMaxHistory());System.out.println("啟動時清理: " + rollingPolicy.isCleanHistoryOnStart());return fileAppender;}/*** 重新創建所有已緩存的Logger(應用新的控制臺輸出設置)*/public static void refreshAllLoggers() {System.out.println("開始刷新所有Logger");clearAllLoggers();System.out.println("所有Logger已刷新完成");}/*** 清理指定類名的日志對象緩存* @param clazz 類對象*/public static void clearLogger(Class<?> clazz) {clearLogger(clazz, DEFAULT_LOG_DIR);}/*** 清理指定類名和目錄的日志對象緩存* @param clazz 類對象* @param logDir 日志文件目錄*/public static void clearLogger(Class<?> clazz, String logDir) {String className = clazz.getName();String key = className + "_" + logDir;Logger logger = loggerCache.remove(key);if (logger != null) {logger.detachAndStopAllAppenders();}}/*** 清理所有日志對象緩存*/public static void clearAllLoggers() {loggerCache.values().forEach(logger -> logger.detachAndStopAllAppenders());loggerCache.clear();}/*** 獲取緩存中的日志對象數量* @return 日志對象數量*/public static int getLoggerCount() {return loggerCache.size();}public static void main(String[] args) {String logDir1 = "E:\\logs\\1";String logDir2 = "E:\\logs\\2";System.out.println("=== 開始測試日志功能 ===");// 測試不同類名的日志對象 - 使用Class對象Logger log1 = LogUtil.getLogger(TestClass1.class, logDir1);Logger log2 = LogUtil.getLogger(TestClass2.class, logDir2);// 測試日志輸出System.out.println("=== 開始輸出日志 ===");log1.info("TestClass1的日志信息: {}", "這是第一條日志");log1.error("TestClass1的錯誤日志: {}", "這是錯誤信息");log2.info("TestClass2的日志信息: {}", "這是第二條日志");log2.warn("TestClass2的警告日志: {}", "這是警告信息");}// 測試用的內部類public static class TestClass1 {}public static class TestClass2 {}
}
執行結果