用java实现一个自定义基于logback的日志工具类

发布于:2025-07-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

✅ 动态创建: 无需配置文件,通过代码动态创建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());
        
        // 获取LoggerContext
        LoggerContext 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
        
        // 清除已有的Appender
        logger.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}.log
        String 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 {}
}

执行结果


网站公告

今日签到

点亮在社区的每一天
去签到