本文基于SpringBoot2.5.2 来讲解SpringBoot中如何动态修改日志级别
为什么要动态修改日志
为了避免大量日志对磁盘的占用,大量的日志甚至会影响系统性能,项目上线后一般会将日志级别设置成warn或者error级别。但是有时候为了查找bug,需要查看更低级别的日志信息,如果是修改日志级别重启,这是一个比较繁琐的过程,bug也不一定能够复现,因此需要能够在线上动态的修改日志级别。
其实spring-boot-starter-actuator
已经提供了日志级别动态变更功能,和本文所讲实现原理其实是一样的,都是从Spring的上下文中拿到LoggingSystem
,通过LoggingSystem
来调用修改日志级别。但是如果仅仅是为了实现该功能去依赖spring-boot-starter-actuator
,会使得工程显得有点笨重。
actuator中的实现参考如下两个类
org.springframework.boot.actuate.autoconfigure.logging.LoggersEndpointAutoConfiguration#loggersEndpoint
org.springframework.boot.actuate.logging.LoggersEndpoint
如下是本文所讲,SpringBoot动态修改日志级别的具体实现
为什么实现代码如下,建议先看明白SpringBoot中的 org.springframework.boot.context.logging.LoggingApplicationListener
这个只能针对当前单进程有效,示例代码只是用来了解其背后的原理,要想在分布式系统的所有进程中生效,可以对接配置中心来监听配置变更,利用LoggingSystem
来动态修改日志级别.
@RequestMapping("/log")
@RestController
public class LoggerLevelController {
private static final Map<String, LogLevel> LEVELS = new HashMap<>();
static {
LEVELS.put("trace", LogLevel.TRACE);
LEVELS.put("debug", LogLevel.DEBUG);
LEVELS.put("info", LogLevel.INFO);
LEVELS.put("warn", LogLevel.WARN);
LEVELS.put("error", LogLevel.ERROR);
LEVELS.put("off", LogLevel.OFF);
}
@Autowired
private ApplicationContext context;
/** 设置日志级别 */
@RequestMapping("/level/{loggerConf}")
public List<String> setLevel(@PathVariable(name = "loggerConf") String loggerConf) {
String[] logger = loggerConf.split("=");
String loggerName = logger[0];
String level = logger[1];
LogLevel logLevel = LEVELS.getOrDefault(level, LogLevel.INFO);
LoggingSystem loggingSystem = context.getBean(LoggingApplicationListener.LOGGING_SYSTEM_BEAN_NAME, LoggingSystem.class);
//按分组设置日志级别
LoggerGroups loggerGroups = context.getBean(LoggingApplicationListener.LOGGER_GROUPS_BEAN_NAME, LoggerGroups.class);
LoggerGroup group = loggerGroups.get(loggerName);
if (group != null && group.hasMembers()) {
group.configureLogLevel(logLevel, loggingSystem::setLogLevel);
List<String> loggerConfigs = group.getMembers().stream()
.map(loggingSystem::getLoggerConfiguration)
.sorted(Comparator.comparing(LoggerConfiguration::getName))
.map(LoggerConfiguration::toString)
.collect(Collectors.toList());
return loggerConfigs;
}
//只针对单个loggerName设置日志级别
LoggerConfiguration loggerConfiguration = loggingSystem.getLoggerConfiguration(loggerName);
//如果loggerName对应的Logger不存在不应该修改其日志级别,如果日志使用的是logback,可能导致内存泄漏
//参见 ch.qos.logback.classic.LoggerContext.getLogger(java.lang.String) L142
//在SpringBoot2.0.9中这个判断不生效,即使loggerName不存在同样能够获取的LoggerConfiguration,注意测试
if (loggerConfiguration == null) {
return Collections.singletonList("不存在loggerName=" + loggerName);
}
loggingSystem.setLogLevel(loggerName, logLevel);
return Collections.singletonList(loggingSystem.getLoggerConfiguration(loggerName).toString());
}
/** 获取所有的日志级别设置 */
@RequestMapping("/confs")
public List<LoggerConfiguration> allLoggerConfigs() {
LoggingSystem loggingSystem = context.getBean(LoggingApplicationListener.LOGGING_SYSTEM_BEAN_NAME, LoggingSystem.class);
return loggingSystem.getLoggerConfigurations()
.stream()
.sorted(Comparator.comparing(LoggerConfiguration::getName))
.collect(Collectors.toList());
}
@RequestMapping("/set")
public String log(String level) {
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
return level;
}
}
修改日志级别
http://localhost:8080/log/level/ROOT=info
查看日志级别
http://localhost:8080/log/confs