您现在的位置是:首页 >技术杂谈 >SpringBoot/logback日志文件独立【多日志文件输出】网站首页技术杂谈
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
的目录,里面是存放的独立日志。
需要注意的是,这里需要把该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
,后面再调用则是引用已经创建的了。
这里目录的名称就是创建Logger的时候传入的moduleName
logFlag
的含义
这样配图应该看的比较清晰,logFlag
就是在我们每一行输出时能打印出来的内容,也就是设置的LoggerName,完整的LoggerName
就是moduleName-[logFlag]
,如果特殊情况,可以传入当前类的.class.getName()
最后
最后还是感谢原作者输出的博客,我也是根据他的内容做的修改。
标记一下出处:https://zhuanlan.zhihu.com/p/447231711