✅ 动态创建: 无需配置文件,通过代码动态创建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 {}
}
执行结果