1,為什么日志打印級別要動態調整?
隨着項目越來越大,訪問量也越來越高,遇到問題時想要排查,可是日志一打開卻刷的太快太快,不好排查問題,有的時候甚至因為短時間打印日志太多,嚴重影響了性能,這個時候日志的打印級別的動態調整就相當有必要了,在不重啟項目的情況,不改動代碼的情況下,通過Apollo動態配置就可以通過配置動態的調整日志的級別,可以精確到配置具體的類的日志打印級別。
2,動態調整的方案
大致思路為在springboot項目啟動之后,讀取Apollo配置文件里的配置文件,總共有兩個,一個是總的日志級別,一個是單獨的類的配置,然后設置總的之后再設置具體到類的自定義的,同時注冊一個監聽器監聽兩個文件的變化,一旦有變化就重新設置一遍,是不是很簡單呢?
在項目中使用日志的方式請統一使用Slf4j門面模式。具體代碼如下,將該類在啟動時注冊入spring容器就行。值得注意的是該類中的initCustomClass()方法,該方法是因為有很多類在springboot啟動時沒有初始化,那么也就沒有注冊入LoggerContext的屬性中,所以是無法設置的,通過手動初始化該類的形式來初始化之后重新設置一遍。在詳細的配置文件中是支持正則表達式來匹配的。
@Service
@Slf4j
public class LoggerConfiguration implements ConfigChangeListener, ApplicationListener<ContextRefreshedEvent> {
private static final String LOGGER_LEVEL = "logger_level";
private static final String LOGGER_LEVEL_DETAIL = "logger_level_detail";
private static final String DEFAULT_LEVEL = "error";
private static final String INFO_LEVEL = "info";
private Config applicationConfig;
public LoggerConfiguration(Config applicationConfig) {
this.applicationConfig = applicationConfig;
}
@Override
public void onChange(ConfigChangeEvent changeEvent) {
if (changeEvent.changedKeys().contains(LOGGER_LEVEL)) {
String newValue = changeEvent.getChange(LOGGER_LEVEL).getNewValue();
try {
log.info("update rootLoggerLevel {}", newValue);
setRootLoggerLevel(newValue);
} catch (Exception e) {
log.error("loggerLevel onChange failed {}", ExceptionUtil.stacktraceToString(e));
}
}
if (changeEvent.changedKeys().contains(LOGGER_LEVEL_DETAIL)) {
String newValue = changeEvent.getChange(LOGGER_LEVEL_DETAIL).getNewValue();
try {
log.info("update loggerLevel detail {}", newValue);
parseLoggerConfig(newValue);
} catch (Exception e) {
log.error("loggerLevel detail onChange failed {}", ExceptionUtil.stacktraceToString(e));
}
}
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
try {
// 初始化風控監聽action配置
String level = applicationConfig.getProperty(LOGGER_LEVEL, DEFAULT_LEVEL);
log.info("init root loggerLevel {}", level);
setRootLoggerLevel(level);
// 注冊配置監聽
applicationConfig.addChangeListener(this);
} catch (Exception e) {
log.error("loggerLevel init failed {}", ExceptionUtil.stacktraceToString(e));
}
}
/**
* 將未注冊進日志容器的類處初始化
*
* @param className
*/
private boolean initCustomClass(String className) {
try {
Class.forName(className);
return true;
} catch (Exception e) {
log.error("init {} failed", className);
return false;
}
}
private void setRootLoggerLevel(String level) {
try {
Level newLevel = Level.valueOf(level);
LoggerContext logContext = LoggerContext.getContext(false);
Configuration configuration = logContext.getConfiguration();
LoggerConfig loggerConfig = configuration.getRootLogger();
loggerConfig.setLevel(newLevel);
logContext.updateLoggers();
//update后會覆蓋定制化的
setLoggerLevel(this.getClass().getName(), INFO_LEVEL);
reConfig();
log.info("update rootLoggerLevel {}", level);
} catch (Exception e) {
log.error("setRootLoggerLevel failed {}", ExceptionUtil.stacktraceToString(e));
}
}
private void setLoggerLevel(String name, String level) {
try {
Level newLevel = Level.valueOf(level);
LoggerContext logContext = LoggerContext.getContext(false);
//是否沒有匹配到
boolean flag = false;
if (logContext.hasLogger(name)) {
//精確匹配
Logger logger = logContext.getLogger(name);
logger.setLevel(newLevel);
log.info("update {} logger level {}", name, level);
flag = true;
} else {
//正則匹配
Collection<Logger> loggers = logContext.getLoggers();
for (Logger logger : loggers) {
if (Pattern.matches(name, logger.getName())) {
logger.setLevel(newLevel);
log.info("update {} logger level {}", name, level);
flag = true;
}
}
}
//該類未注冊就注冊,注冊失敗那么也就不再繼續設置
if (!flag && initCustomClass(name)) {
//初始化未注冊的類
setLoggerLevel(name, level);
}
} catch (Exception e) {
log.error("setLoggerLevel failed {}", ExceptionUtil.stacktraceToString(e));
}
}
private void reConfig() {
String detail = applicationConfig.getProperty(LOGGER_LEVEL_DETAIL, "");
if (StringUtils.isNotEmpty(detail)) {
parseLoggerConfig(detail);
}
}
private void parseLoggerConfig(String value) {
Map<String, String> config = JSON.parseObject(value, Map.class);
if (config == null) {
return;
}
config.forEach((k, v) -> setLoggerLevel(k, v));
}
public void setApplicationConfig(Config applicationConfig) {
this.applicationConfig = applicationConfig;
}
}
