springboot日志组件加载机制

SpringBoot日志机制

在使用SpringBoot的时候,我们通过在WEB-iNFO/resource目录下,放入logback.xml等文件,或者在application.properties或者applicationo.yml文件中,设置logging.config属性值来指定日志配置文件位置的方式,来初始化应用的日志输出规则。甚至以操作都不做,SpringBoot也会默认使用logback来配置日志格式。本文通过跟踪SpringBoot的启动过程,来探究SpringBoot加载日志组件机制。

1. SpringBoot启动流程

以SpringBoot的2.1.4.RELEASE版本为例。

SpringBoot系统是通过在main方法中,执行run方法开始。一个简单SpringBoot系统的main方法代码如下:

@SpringBootApplication
public class GeektimeApplication {
	public static void main(String[] args) {
		SpringApplication.run(GeektimeApplication.class, args);
	}
}

进入到main方法之后,最终会跟踪到

org.springframework.boot.SpringApplication#run(java.lang.String...)

这个方法中,该方法的源码如下:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    .......
}

getRunListeners(args)中,会读取spring-boot-2.1.4.RELEASE.jar!\META-INF\spring.factories文件,该文件配置了SpringBoot启动中加载的组件。其中listeners中配置了org.springframework.boot.context.logging.LoggingApplicationListener。该类即为日志组件初始化的入口。

2. LoggingApplicationListener类

该类的org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationEvent方法为主要入口方法 。该类的方法如下:

	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationStartingEvent) {
			onApplicationStartingEvent((ApplicationStartingEvent) event);
		}
		else if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent(
					(ApplicationEnvironmentPreparedEvent) event);
		}
		else if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
		}
		else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
				.getApplicationContext().getParent() == null) {
			onContextClosedEvent();
		}
		else if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent();
		}
	}

第3行调用org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationStartingEvent方法,改法继续调用org.springframework.boot.logging.LoggingSystem#get(java.lang.ClassLoader)LoggingSystem类很重要。它相当于一个工厂类,通过get方法去生成不同日志组件的实例。该方法的实现如下:

public static LoggingSystem get(ClassLoader classLoader) {
		String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
		if (StringUtils.hasLength(loggingSystem)) {
			if (NONE.equals(loggingSystem)) {
				return new NoOpLoggingSystem();
			}
			return get(classLoader, loggingSystem);
		}
		return SYSTEMS.entrySet().stream()
				.filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
				.map((entry) -> get(classLoader, entry.getValue())).findFirst()
				.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
	}

其中常量SYSTEM_PROPERTY和SYSTEMS的值如下:

public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
private static final Map SYSTEMS;
	static {
		Map systems = new LinkedHashMap<>();
		systems.put("ch.qos.logback.core.Appender",
				"org.springframework.boot.logging.logback.LogbackLoggingSystem");
		systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
				"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
		systems.put("java.util.logging.LogManager",
				"org.springframework.boot.logging.java.JavaLoggingSystem");
		SYSTEMS = Collections.unmodifiableMap(systems);
	}

所以,如果在程序启动时,不指定参数-Dorg.springframework.boot.logging.LoggingSystem的,会默认从集合SYSTEMS取第一个。因为LogbackLoggingSystem是第一个实现组件,所以会被默认使用。

找到该组件之后,再回到org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationEvent方法,就会发现该方法中的onApplicationxxx方法最终执行的都是LogbackLoggingSystem类中的beforeInitialize、initialize等方法。其中在调用LogbackLoggingSysteminitialize方法之前,会先调用自己的私有org.springframework.boot.context.logging.LoggingApplicationListener#initialize方法,在该方法中会调用到org.springframework.boot.context.logging.LoggingApplicationListener#initializeSystem方法,该方法的源码如下:

	private void initializeSystem(ConfigurableEnvironment environment,
			LoggingSystem system, LogFile logFile) {
		LoggingInitializationContext initializationContext = new LoggingInitializationContext(
				environment);
		String logConfig = environment.getProperty(CONFIG_PROPERTY);
		if (ignoreLogConfig(logConfig)) {
			system.initialize(initializationContext, null, logFile);
		}
		else {
			try {
				ResourceUtils.getURL(logConfig).openStream().close();
				system.initialize(initializationContext, logConfig, logFile);
			}
			catch (Exception ex) {
				// NOTE: We can't use the logger here to report the problem
				System.err.println("Logging system failed to initialize "
						+ "using configuration from '" + logConfig + "'");
				ex.printStackTrace(System.err);
				throw new IllegalStateException(ex);
			}
		}
	}

其中常量CONFIG_PROPERTY值即为logging.config。如果我们配置了logging.config,则会取该配置的值进行初始化。但是此处有个限制,即logging.config配置的值,必须是已xml或者groovy,否则就会报错。具体的加载逻辑,在LogbackLoggingSystem的initialize`中完成。

至此,SpringBoot的日志组件创建并加载配置就完成了。

3.如何自定义加载自己的配置文件

通过以上分析,如果想加载自己配置文件,可以通过配置logging.config文件即可。一般该值配置的为磁盘上某个位置的配置文件,比如d:/logback.xml

但是,现在的分布式系统中,一般都会要求配置统一从一个配置中心获取,便于统一管理。所以,如果要加载一个远程配置怎么配置。

方式一 配置远程访问地址:

其实logging.config配置的值可以是一个网络请求的,只要我们在请求的结尾贬值该请求返回的是xml还是groovy即可。比如,如果使用nacos作为配置中心的话,可以配置如下的值:

http://172.16.10.77:8848/nacos/v1/cs/configs?dataId=自定义的dataId&group=自定义group&xml

通过该方式,也是可以初始化成功的。

以上的方式虽然可以,但是有个局限条件,如果配置中心增加了权限控制的话,就不能通过http请求直接获取配置信息了。所以可以采用以下的方式,即自定义日志组件。

方式二 自定义日志组件:

我们可以试下自己的日志组件,只要继承了SpringBoot提供的日志组件抽象类AbstractLoggingSystem,实现其抽象方法即可。然后在启动的时候,通过指定启动变量-Dorg.springframework.boot.logging.LoggingSystem的值为自定义日志组件的全类名称即可。比如:

-Dorg.springframework.boot.logging.LoggingSystem=com.zhlong.springboot.learning.geektime.configuration.MyLogbackLoggingSystem

转载于:https://my.oschina.net/u/575836/blog/3043009

你可能感兴趣的:(springboot日志组件加载机制)