背景
随着业务发展,我司几百个应用每天日志打印的量非常的巨大,需要的云成本非常高,这与我司今年降本增效的理念相违背。所以近期收到日志平台的通知,要求各应用清除代码中的非必要的日志打印,从而减少日志流量和存储成本,降低不必要的成本。
分析
虽然要求清除代码中非必要的日志打印,但其实最终目的是减少日志流量和存储成本。方案有以下:
- 全局搜索项目代码,逐行识别是否必要,对非必要代码进行清除;
- 在项目配置文件中,指定包或者类的日志级别,只打印特定级别的日志;
- 结合Apollo动态配置中心,动态配置指定包或者的日志级别;
在以上3种方案中,方案1、2有明显的缺陷。
方案1对所有代码打印日志查找,并且需要对每行日志是否必要进行人工识别界定,需要花费大量的时间和人力,并且可能对后续不太友好;
方案2只是在项目配置文件中进行指定配置,每次配置修改都需要发版。
所以最终我们采用方案3,方案3在方案2基础上,虽然需要接入Apollo配置中心,但所幸项目都已经集成过Apollo,并且方案3在后续对突发问题的排查较为友好。
实现
先交代一下技术背景,我司使用的日志实现库为Log4J。
废话不多说,上代码
@Component
public class LogLevelRefresh implements ConfigChangeListener {
//日志前缀
private static final String PREFIX = "logging.level.";
@PostConstruct
public void init(){
//监听Apollo配置变化
ConfigService.getAppConfig().addChangeListener(this,null, Sets.newHashSet(PREFIX));
}
@Override
public void onChange(ConfigChangeEvent configChangeEvent) {
Set<String> changedKeys = configChangeEvent.changedKeys();
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
for (String changedKey : changedKeys) {
//前缀解析出key
String key = changedKey.substring(PREFIX.length());
Logger logger = loggerContext.getLogger(key);
ConfigChange change = configChangeEvent.getChange(changedKey);
//获取日志打印级别
String level = change.getNewValue();
//如果删除配置,或者级别配置为空,级别设置为null,此时会使用父级Logger的打印级别
if (change.getChangeType() == PropertyChangeType.DELETED || StringUtils.isBlank(level)){
logger.setLevel(null);
continue;
}
logger.setLevel(Level.toLevel(level));
}
}
}
代码比较简单,只要在Apollo配置中心,将跟目录打印级别设置为较高级别,当有突发问题需要排查时,只要在Apollo上将指定包或者类的日志打印级别调整一下,就能保证原有的日志正常打印。