您现在的位置是:首页 >技术杂谈 >SpringBoot/logback日志文件独立【多日志文件输出】网站首页技术杂谈

SpringBoot/logback日志文件独立【多日志文件输出】

Peak_H_ 2024-06-17 11:26:35
简介SpringBoot/logback日志文件独立【多日志文件输出】

背景

在我们业务开发中,有时候有部分代码需要打印日志方便后面追溯、排除问题,但是日志输出的内容又比较多,影响到了查看其他日志,或者某个任务的日志需要单独记录,查看起来更加方便,这时候就需要把日志文件独立出来。

实现

相信使用spring框架的都对logback不陌生,我们要做的就等于是把logback.xml中的配置动态在我们程序中生成。

代码

IndependenceLoggerFactory.java

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.SizeAndTimeBasedRollingPolicy;
import cn.hutool.core.io.FileUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 *@BelongsProject: face-parent
 *@BelongsPackage:
 *@Author: peak
 *@CreateTime: 2023-05-17 16:12
 *@Description: 独立输出日志
 *
 */
public class IndependenceLoggerFactory {

    private IndependenceLoggerFactory() {
    }

    private static LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    private static PatternLayoutEncoder encoder = new PatternLayoutEncoder();
    private static Map<String, Logger> loggerMap = new ConcurrentHashMap<>();
    private static Map<String, RollingFileAppender<ILoggingEvent>> appenderMap = new ConcurrentHashMap<>();
    private static final Object lockObject = new Object();

    static {
        encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n");
        encoder.setCharset(StandardCharsets.UTF_8);
        encoder.setContext(loggerContext);
        encoder.setOutputPatternAsHeader(true);
        encoder.start();
    }

    public static Logger getLogger(IndLogProperty logConfig) {
        if (StringUtils.isBlank(logConfig.getModuleName())) {
            throw new NullPointerException("log module name cannot be empty");
        }
        String logFlag = StringUtils.isBlank(logConfig.getLogFlag()) ? logConfig.getModuleName() : logConfig.getModuleName() + "-[" + logConfig.getLogFlag() + "]";
        if (loggerMap.containsKey(logFlag)) {
            return loggerMap.get(logFlag);
        }
        synchronized (lockObject) {
            if (loggerMap.containsKey(logFlag)) {
                return loggerMap.get(logFlag);
            }
            String moduleName = logConfig.getModuleName();
            /*********输出到文件的配置 start*************/
            //该LOG_HOME取自logback.xml里面的环境变量
            String logHome = Optional.ofNullable(loggerContext.getProperty("LOG_HOME")).orElseGet(() -> "/tmp/logs");
            String logPath = logHome + FileUtil.FILE_SEPARATOR + moduleName + FileUtil.FILE_SEPARATOR;
            RollingFileAppender<ILoggingEvent> rollingFileAppender = appenderMap.computeIfAbsent(moduleName, k -> new RollingFileAppender<>());
            if (!rollingFileAppender.isStarted()) {
                rollingFileAppender.setContext(loggerContext);
                rollingFileAppender.setAppend(true);
                rollingFileAppender.setName(moduleName);
                rollingFileAppender.setFile(logPath + "stdout.log");
                SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
                rollingPolicy.setFileNamePattern(logPath + "/stdout-%d{yyyyMMdd}.log.%i");
                rollingPolicy.setMaxFileSize(logConfig.getMaxFileSize());
                rollingPolicy.setMaxHistory(logConfig.getMaxHistory());
                rollingPolicy.setTotalSizeCap(logConfig.getTotalSizeCap());
                rollingPolicy.setContext(loggerContext);
                rollingPolicy.setParent(rollingFileAppender);
                rollingPolicy.start();
                rollingFileAppender.setRollingPolicy(rollingPolicy);
                rollingFileAppender.setEncoder(encoder);
                rollingFileAppender.start();
                /*********输出到文件的配置 end*************/
            }
            /*********输出到控制台的配置 start*************/
            //控制台输出
            ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
            consoleAppender.setEncoder(encoder);
            consoleAppender.start();
            /*********输出到控制台的配置 end*************/
            ch.qos.logback.classic.Logger rootLogger = loggerContext.getLogger(logFlag);
            rootLogger.setLevel(logConfig.getLevel());
            rootLogger.addAppender(rollingFileAppender);
            rootLogger.addAppender(consoleAppender);
            rootLogger.setAdditive(logConfig.isAdditive());
            loggerMap.put(logFlag, rootLogger);
            return rootLogger;
        }
    }
}

这里有获取logback.xml中定义的LOG_HOME变量,这个变量的值就是主日志文件输出的位置,为了统一,这些独立输出的日志也会在主日志目录下新建一个moduleName的目录,里面是存放的独立日志。
image.png
需要注意的是,这里需要把该property的scope属性设置成context,否则会在代码获取不到。

IndLogProperty.java

import ch.qos.logback.classic.Level;
import ch.qos.logback.core.util.FileSize;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 *@BelongsProject: face-parent
 *@BelongsPackage: 
 *@Author: peak
 *@CreateTime: 2023-05-18 10:26
 *@Description: 独立日志配置
 */
@Data
@Accessors(chain = true)
@AllArgsConstructor
public class IndLogProperty {
    /**
     * 模块名称
     */
    private String moduleName;
    /**
     * 日志标记
     */
    private String logFlag;
    /**
     * 单个文件最大大小
     */
    private FileSize maxFileSize = FileSize.valueOf("100MB");
    /**
     * 保留天数
     */
    private Integer maxHistory = 10;
    /**
     * 文件总大小
     */
    private FileSize totalSizeCap = FileSize.valueOf("3GB");
    /**
     * 打印日志级别
     */
    private Level level = Level.INFO;
    /**
     * 是否输出到父日志文件中
     */
    private boolean additive = true;

    public IndLogProperty(String moduleName) {
        this.moduleName = moduleName;
    }
}

这里就是创建Logger的一些配置,单个文件大小,总文件大小等等…其中additive代表的意思就是是否往父日志中输出,如果是true,那么主日志文件中也会输出这些内容,如果只需要单独输出到我们配置的这个moduleName目录下,那么可以配置成false
其中moduleName属性是必填的,这是目录名称,也是根据它来创建或引用前面已经创建的Appender,注意,同一个moduleName,只有第一次会创建Appender,后面再调用则是引用已经创建的了。
image.png

这里目录的名称就是创建Logger的时候传入的moduleName

logFlag的含义
image.png
image.png
这样配图应该看的比较清晰,logFlag就是在我们每一行输出时能打印出来的内容,也就是设置的LoggerName,完整的LoggerName就是moduleName-[logFlag],如果特殊情况,可以传入当前类的.class.getName()

最后

最后还是感谢原作者输出的博客,我也是根据他的内容做的修改。
标记一下出处:https://zhuanlan.zhihu.com/p/447231711

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。