背景
目前常用的实现动态配置日志级别的应该非SpringBoot
的spring-boot-starter-actuator
莫属了。
不过通过spring-boot-starter-actuator
配置的日志级别,服务一旦重启就会恢复原状。且只能通过访问指定的接口来修改单个实例的日志级别(SpringBootAdmin也是一样,只能修改单个实例的)。如果是想修改某个服务所有实例的日志级别,只能修改配置文件,然后重启服务,可以说局限性稍微大点儿。
由于重启服务太费劲儿,所以想到了利用Apollo
配置中心来动态修改日志级别。
实现
大体思路是通过Apollo
的监听机制,结合Spring的事件监听,来刷新日志级别。
具体代码如下:
LogLevelRefreshEvent
:自定义Spring事件。
import lombok.ToString;
import org.springframework.context.ApplicationEvent;
/**
* @author lifengdi
* @date 2021/12/24 11:29
*/
@ToString
public class LogLevelRefreshEvent extends ApplicationEvent {
/**
* key
*/
private final String key;
/**
* 旧值
*/
private final Object oldValue;
/**
* 新值
*/
private final Object newValue;
public LogLevelRefreshEvent(String key, Object oldValue, Object newValue) {
super(key);
this.key = key;
this.oldValue = oldValue;
this.newValue = newValue;
}
public String getKey() {
return key;
}
public Object getOldValue() {
return oldValue;
}
public Object getNewValue() {
return newValue;
}
}
LogConfig
:日志级别配置文件,用于获取Apollo
配置以及转换日志级别。
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import lombok.Getter;
import org.springframework.boot.logging.LogLevel;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @author lifengdi
* @date 2021/12/24 14:29
*/
@Configuration
@Getter
public class LogConfig {
/**
* 日志级别配置key
*/
public static final String LOG_LEVEL_CONFIG_KEY = "logLevel.list";
/**
* 日志级别配置,格式:
* <code>
* {
* "com.cowell.conveyor": "warn",
* "com.cowell.tools": "warn"
* }
* </code>
*/
@ApolloJsonValue("${logLevel.list:{}}")
private Map<String, String> logLevelConfig;
/**
* 日志级别映射
*/
private final Map<String, LogLevel> levelMap = new HashMap<String, LogLevel>() {
{
put("trace", LogLevel.TRACE);
put("debug", LogLevel.DEBUG);
put("info", LogLevel.INFO);
put("warn", LogLevel.WARN);
put("error", LogLevel.ERROR);
put("fatal", LogLevel.FATAL);
put("off", LogLevel.OFF);
}
};
public Map<String, LogLevel> getLevelMap() {
return levelMap;
}
/**
* 根据名称获取日志级别,默认返回debug级别。
* @param logLevel 级别名称
* @return 日志级别 {@link LogLevel}
*/
public LogLevel getFromLevelMap(String logLevel) {
return levelMap.getOrDefault(logLevel, LogLevel.DEBUG);
}
}
LogLevelUtils
:工具类
import com.lifengdi.config.LogConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingSystem;
import java.util.List;
import java.util.Map;
/**
* @author lifengdi
* @date 2021/12/27 17:44
*/
public class LogLevelUtils {
private static final Logger logger = LoggerFactory.getLogger(LogLevelUtils.class);
/**
* 动态刷新日志级别
* @param loggingSystem LoggingSystem
* @param logConfig LogConfig
*/
public static void refreshLogLevel(LoggingSystem loggingSystem, LogConfig logConfig) {
Map<String, String> logLevelConfig = logConfig.getLogLevelConfig();
List<LoggerConfiguration> loggerConfigurations = loggingSystem.getLoggerConfigurations();
if (!logLevelConfig.isEmpty()) {
logLevelConfig.forEach((loggerName, value) -> {
String logLevel = value.toLowerCase();
for (LoggerConfiguration loggerConfiguration : loggerConfigurations) {
if (loggerConfiguration.getName().startsWith(loggerName)) {
loggingSystem.setLogLevel(loggerName, logConfig.getFromLevelMap(logLevel));
logger.debug("LogLevelUtils|RefreshLogLevel|SUCCESS|loggerName:{},logLevel:{}", loggerName, logLevel);
}
}
});
}
}
}
日志级别刷新Listener:
import com.lifengdi.config.LogConfig;
import com.lifengdi.util.LogLevelUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 日志级别刷新Listener
*
* @author lifengdi
* @date 2021/12/24 11:34
*/
@Component
@Slf4j
public class LogLevelRefreshListener implements ApplicationListener<LogLevelRefreshEvent> {
@Autowired
private LoggingSystem loggingSystem;
@Autowired
private LogConfig logConfig;
@Override
public void onApplicationEvent(LogLevelRefreshEvent logLevelRefreshEvent) {
if (LogConfig.LOG_LEVEL_CONFIG_KEY.equals(logLevelRefreshEvent.getKey())) {
log.info("LogLevelRefreshListener|onApplicationEvent|refreshLogLevel|configRefreshEvent:{}", logLevelRefreshEvent);
LogLevelUtils.refreshLogLevel(loggingSystem, logConfig);
log.info("LogLevelRefreshListener|onApplicationEvent|refreshLogLevel|SUCCESS|configRefreshEvent:{}", logLevelRefreshEvent);
}
}
}
服务启动时日志级别刷新Listener:
import com.lifengdi.config.LogConfig;
import com.lifengdi.util.LogLevelUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 服务启动时日志级别刷新Listener
*
* @author lifengdi
* @date 2021/12/24 13:59
*/
@Component
public class LogLevelApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {
@Autowired
private LoggingSystem loggingSystem;
@Autowired
private LogConfig logConfig;
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
LogLevelUtils.refreshLogLevel(loggingSystem, logConfig);
}
}
增加事件发布:
@Component
public class ApolloAutoRefreshBean {
private final Logger logger = LoggerFactory.getLogger(ApolloAutoRefreshBean.class);
@Autowired
private ApplicationContext applicationContext;
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) {
logger.info("ApolloAutoRefreshBean|onChange|apollo触发变更|param:namespace={}", changeEvent.getNamespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
logger.info("ApolloAutoRefreshBean|onChange|apollo数据key-value发生变更|key: {}, oldValue: {}, newValue: {}, changeType: {}",
change.getPropertyName(), change.getOldValue(), change.getNewValue(),
change.getChangeType());
applicationContext.publishEvent(new LogLevelRefreshEvent(key, change.getOldValue(), change.getNewValue()));
}
applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
}
}
配置中心配置的参数格式如下:
{
"com.lifengdi.util": "info",
"com.lifengdi.tools": "warn"
}
至此,动态配置日志级别算是搞定了。
除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接