系列
实现自定义SpringBoot框架日志组件の一:日志系统
实现自定义SpringBoot框架日志组件の二:配置文件
实现自定义SpringBoot框架日志组件の三: 自定义pattern
github代码地址
前言
前面的内容一直没有实现自适应
,这里实现一下
先描述一下作用:
因为我们的代码不知道使用方的包名,所以无法在 dev/local 环境自动设置日志级别为 debug,导致根本无法使用,想要使用还得新增配置文件,比较麻烦,所以我们想
自适应
,自动拿到包名,自动根据环境设置不同的级别
包名
包名的判断依据很好说,就是启动类的注解SpringBootApplication
的参数 scanBasePackages
怎么拿到呢? spring 提供了很多的扩展点,选哪一个呢?
- 因为日志组件基本是第一个加载的,因为后续所有代码都有可能打印日志
- 业务代码自己也有可能去使用spring 的扩展点
基于上面两个因素,我决定选一个非常早的扩展点 org.springframework.context.ApplicationContextInitializer
这个扩展点是在spring上下文初始化之前执行的
YellowApplicationContextInitializer
首先在spring.factories
里面新增 org.springframework.context.ApplicationContextInitializer=com.github.hwhaocool.log.YellowApplicationContextInitializer
因为之前已经新增了日志系统,所以此时文件完整内容如下
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
com.github.hwhaocool.log.YellowLog4J2LoggingSystem.Factory
org.springframework.context.ApplicationContextInitializer=com.github.hwhaocool.log.YellowApplicationContextInitializer
我们实现的时候思路如下
扩展点 --> Initializer --> 得到package --> 设置级别
因为后面二者是靠第二个点触发的,代码写在一起不合适,而且后两者目的也不太一样,为了方便后续扩展,那就独立一个接口出来吧
大概是这样的,
-
YellowApplicationContextInitializer
添加到spring.factories
里被spring触发 -
YellowApplicationContextInitializer
触发IContextInitializer
-
BasePackageHolderInitializer
先触发,获取、存储包名 -
LoggerLevelInitializer
获取包名,设置级别
所以 代码如下
package com.github.hwhaocool.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import java.util.Comparator;
import java.util.List;
/**
*
* @author yellowtail
* @since 2022/8/27 23:59
*/
public class YellowApplicationContextInitializer implements ApplicationContextInitializer, Ordered {
final Logger LOGGER = LoggerFactory.getLogger(YellowApplicationContextInitializer.class);
static final List contextInitializerList = List.of(
new BasePackageHolderInitializer(),
new LoggerLevelInitializer()
);
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("start initialize");
}
// 上下文初始化的时候,先排好序,再依次触发
contextInitializerList.stream()
.sorted(Comparator.comparingInt(IContextInitializer::getOrder))
.forEach(iContextInitializer -> {
try {
iContextInitializer.initialize(applicationContext);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("initialize success, {}", iContextInitializer.getClass().getSimpleName());
}
} catch (Exception e) {
LOGGER.error("initialize {} occur error, ", iContextInitializer.getClass().getSimpleName(), e);
}
});
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("load all initializer finish");
}
}
}
接口 IContextInitializer
如下
package com.github.hwhaocool.log;
import org.springframework.context.ConfigurableApplicationContext;
public interface IContextInitializer {
void initialize(ConfigurableApplicationContext applicationContext);
int getOrder() ;
}
两个方法
-
getOrder
执行顺序,有了这个,当需要改变顺序的时候,修改实现类就行,不用老是去改调用方代码 -
initialize
逻辑
BasePackageHolderInitializer
这个是获取包名的
我想了好久,没有思路,最后采用了一种笨办法,代码扫描 org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
package com.github.hwhaocool.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author yellowtail
* @since 2022/8/30 21:54
*/
public class BasePackageHolderInitializer implements IContextInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(BasePackageHolderInitializer.class);
@Override
public int getOrder() {
return 0;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 新建扫描器,不使用默认的过滤器
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
// 新增一个过滤器:找到带有注解 SpringBootApplication 的类
scanner.addIncludeFilter(new AnnotationTypeFilter(SpringBootApplication.class));
// 扫描范围是 所有
Set candidateComponents = scanner.findCandidateComponents("");
for (BeanDefinition candidateComponent : candidateComponents) {
Class> clazz;
try {
clazz = applicationContext.getClassLoader().loadClass(candidateComponent.getBeanClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
SpringBootApplication annotation = AnnotationUtils.findAnnotation(clazz, SpringBootApplication.class);
if (null == annotation) {
continue;
}
List list = null;
String[] strings = annotation.scanBasePackages();
if (null == strings || strings.length == 0) {
// 没有写注解,就用类的包名
String packageName = clazz.getPackageName();
list = Collections.singletonList(packageName);
} else {
list = Arrays.asList(strings);
}
LOGGER.info("basePackage {}", list);
BasePackageHolder.setBasePackages(list);
}
}
}
因为不知道使用方的代码包名,所以只能全局扫描,所以性能比较低,会增加启动耗时(后续可以考虑使用环境变量等修改扫描范围)
其中BasePackageHolder
只是一个静态类
public class BasePackageHolder {
static List basePackages;
public static List getBasePackages() {
return basePackages;
}
public static void setBasePackages(List basePackages) {
BasePackageHolder.basePackages = basePackages;
}
}
LoggerLevelInitializer
逻辑比较简单,拿到包名,设置一下级别就行
package com.github.hwhaocool.log;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 对业务日志进行自适应初始化
* @author yellowtail
* @since 2022/8/30 21:54
*/
public class LoggerLevelInitializer implements IContextInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggerLevelInitializer.class);
@Override
public int getOrder() {
return 1;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
YellowLog4J2LoggingSystem.ProfileEnum profileEnum = ProfileHolder.getProfileEnum();
if (YellowLog4J2LoggingSystem.ProfileEnum.LOCAL == profileEnum || YellowLog4J2LoggingSystem.ProfileEnum.DEV == profileEnum) {
for (String basePackage : BasePackageHolder.getBasePackages()) {
if (basePackage.startsWith("com.github.hwhaocool")) {
// 已经配置过了
continue;
}
Configurator.setLevel(basePackage, Level.toLevel("debug"));
LOGGER.info("set debug level for {}", basePackage);
}
}
}
}