logback源码阅读(三) springboot对LoggingSystem日志系统的支持

logback源码阅读(一)获取ILoggerFactory、LoggerContextInitializer.autoConfig()的findURLOfDefaultConfigurationFile方法中,我们知道默认配置配置文件是依次按照logback.configurationFile,logback-test.xml,logback.xml得到。但是很多项目中是这么配置的。

logging:
  config: classpath:logback-ae.xml
  1. 这样配置的优势是什么?由于标准的logback.xml配置文件加载的太早,所以你不能在里面使用扩展部分。你需要使用logback-spring.xml或者通过logging.config自定义比如读取系统变量等
  2. 这样配置是怎么加载的配置文件?

回答第2个问题之前,希望读者已经了解过springboot的启动流程
springboot启动原理
以及logback源码阅读(一)获取ILoggerFactory、Logger
接下来,我会结合启动springboot的启动流程一探究竟,springboot是怎么支持日志系统的

ConfigurableApplicationContext run(String… args)

该方法是springboot的启动最外层方法

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

SpringApplicationRunListeners listeners = getRunListeners(args)

这个方法会从META-INF/spring.factories 中读取Key为 org.springframework.boot.SpringApplicationRunListener 的Values,比如在spring-boot包中的定义的spring.factories:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener


public interface SpringApplicationRunListener {
    void starting();

    void environmentPrepared(ConfigurableEnvironment environment);

    void contextPrepared(ConfigurableApplicationContext context);

    void contextLoaded(ConfigurableApplicationContext context);

    void started(ConfigurableApplicationContext context);

    void running(ConfigurableApplicationContext context);

    void failed(ConfigurableApplicationContext context, Throwable exception);
}

它主要是负责发布SpringApplicationEvent事件的,它会利用一个内部的ApplicationEventMulticaster在上下文实际被刷新之前对事件进行处理。

LoggingApplicationListener

  1. LoggingApplicationListener是配置 LoggingSystem 的 ApplicationListener。
  2. 如果环境包含 logging.config 属性,它将用于引导日志系统,否则使用默认配置。
  3. 无论如何,如果环境包含 logging.level,则日志级别将被自定义。条目和日志记录组可以使用 logging.group 定义。
  4. 默认情况下,日志输出仅写入控制台。如果需要日志文件,可以使用 logging.path 和 logging.file 属性
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();
		}
	}

onApplicationEvent方法根据多播器派发的事件对loggingSystem做不同的处理

onApplicationStartingEvent

当sprignboot刚启动的时候listeners.starting();,会执行loggingSystem.beforeInitialize(),此时环境变量还没有被设置

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
		this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		this.loggingSystem.beforeInitialize();
	}

LoggingSystem的获取就是从类路径加载一下类

static {
		Map<String, String> 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);
	}

可以看到现在支持LogbackLoggingSystem,Log4J2LoggingSystem和JavaLoggingSystem,如果加载了多个就取第一个,默认LogbackLoggingSystem

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"));
	}

然后调用beforeInitialize()方法

public void beforeInitialize() {
		LoggerContext loggerContext = getLoggerContext();
		if (isAlreadyInitialized(loggerContext)) {
			return;
		}
		super.beforeInitialize();
		loggerContext.getTurboFilterList().add(FILTER);
	}

如果loggerContext没有被初始化过,则调用父类beforeInitialize()删除Jdk Logging Bridge 处理程序,需要关注的是,最后加了一个过滤器阻止日志的打印也就是说一直到springboot启动的下一个阶段都不会打印日志

private static final TurboFilter FILTER = new TurboFilter() {

		@Override
		public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, Level level, String format,
				Object[] params, Throwable t) {
			return FilterReply.DENY;
		}

	};

ApplicationEnvironmentPreparedEvent

这个时间发生在springboot启动的prepareEnvironment过程,这个阶段配置文件全部都加载完成,环境变量也都构建完毕。此时可以初始化日志系统(LoggingSystem)了

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		if (this.loggingSystem == null) {
			this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		}
		initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
	}

先判断loggingSystem是否存在,不存在则获取,最后调用initialize方法

protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
		new LoggingSystemProperties(environment).apply();
		this.logFile = LogFile.get(environment);
		if (this.logFile != null) {
			this.logFile.applyToSystemProperties();
		}
		initializeEarlyLoggingLevel(environment);
		initializeSystem(environment, this.loggingSystem, this.logFile);
		initializeFinalLoggingLevels(environment, this.loggingSystem);
		registerShutdownHookIfNecessary(environment, this.loggingSystem);
	}

new LoggingSystemProperties(environment).apply();apply方法定义了系统变量与日志系统间参数的映射关系

```java
public void apply() {
		apply(null);
	}

	public void apply(LogFile logFile) {
		PropertyResolver resolver = getPropertyResolver();
		setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "exception-conversion-word");
		setSystemProperty(PID_KEY, new ApplicationPid().toString());
		setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "pattern.console");
		setSystemProperty(resolver, FILE_LOG_PATTERN, "pattern.file");
		setSystemProperty(resolver, FILE_MAX_HISTORY, "file.max-history");
		setSystemProperty(resolver, FILE_MAX_SIZE, "file.max-size");
		setSystemProperty(resolver, LOG_LEVEL_PATTERN, "pattern.level");
		setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "pattern.dateformat");
		if (logFile != null) {
			logFile.applyToSystemProperties();
		}
	}

this.logFile = LogFile.get(environment);这个方法从logging.file和logging.path来获取日志输出的文件或者位置

initializeEarlyLoggingLevel方法用来初始化springboot早期的日志级别

private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) {
		if (this.parseArgs && this.springBootLogging == null) {
			if (isSet(environment, "debug")) {
				this.springBootLogging = LogLevel.DEBUG;
			}
			if (isSet(environment, "trace")) {
				this.springBootLogging = LogLevel.TRACE;
			}
		}
	}

initializeSystem(environment, this.loggingSystem, this.logFile);初始化日志系统

private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
		LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
		//读取logging.config
		String logConfig = environment.getProperty(CONFIG_PROPERTY);
		if (ignoreLogConfig(logConfig)) {//logging.config配置的-D开头则忽略该配置
			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);
			}
		}
	}

initialize则是根据configLocation和logFile进行初始化

public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
		if (StringUtils.hasLength(configLocation)) {
			initializeWithSpecificConfig(initializationContext, configLocation, logFile);
			return;
		}
		initializeWithConventions(initializationContext, logFile);
	}

最后loggerContext.getTurboFilterList().remove(FILTER);去掉之前加入的过滤器,日志系统开始能打印日志markAsInitialized(loggerContext)将loggerContext标记为已经初始化

initializeFinalLoggingLevels(environment, this.loggingSystem);方法读取logger.level前缀的配置来调整最终的日志级别

private void initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) {
		if (this.springBootLogging != null) {
			initializeLogLevel(system, this.springBootLogging);
		}
		setLogLevels(system, environment);
	}

如果springBootLogging不为空,先设置日志级别为springBootLogging

setLogLevels获取logger.level前缀的配置来调整日志级别,配置文件可以是apollo,也可以是application.yml或者下图的任意配置
在这里插入图片描述

protected void setLogLevels(LoggingSystem system, Environment environment) {
		if (!(environment instanceof ConfigurableEnvironment)) {
			return;
		}
		Binder binder = Binder.get(environment);
		Map<String, String[]> groups = getGroups();
		binder.bind(LOGGING_GROUP, STRING_STRINGS_MAP.withExistingValue(groups));
		Map<String, String> levels = binder.bind(LOGGING_LEVEL, STRING_STRING_MAP).orElseGet(Collections::emptyMap);
		levels.forEach((name, level) -> {
			String[] groupedNames = groups.get(name);
			if (ObjectUtils.isEmpty(groupedNames)) {
				setLogLevel(system, name, level);
			}
			else {
				setLogLevel(system, groupedNames, level);
			}
		});
	}

setLogLevel方法可以结合之前的文章读者自行阅读

ApplicationPreparedEvent

这个事件发生在springboot启动的prepareContext阶段prepareContext(context, environment, listeners, applicationArguments, printedBanner);
此刻容器上下文已经准备好

private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
		ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
		if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
			beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
		}
		if (this.logFile != null && !beanFactory.containsBean(LOGFILE_BEAN_NAME)) {
			beanFactory.registerSingleton(LOGFILE_BEAN_NAME, this.logFile);
		}
	}

这个方法就是把loggingSystem和logFile注册进容器

ContextClosedEvent || ApplicationFailedEvent

这两个阶段分别是容器关闭和SpringBoot启动失败,都需要做一些清理动作

private void onContextClosedEvent() {
		if (this.loggingSystem != null) {
			this.loggingSystem.cleanUp();
		}
	}

	private void onApplicationFailedEvent() {
		if (this.loggingSystem != null) {
			this.loggingSystem.cleanUp();
		}
	}

都是调用cleanUp方法

public void cleanUp() {
		LoggerContext context = getLoggerContext();
		markAsUninitialized(context);
		super.cleanUp();
		context.getStatusManager().clear();
		context.getTurboFilterList().remove(FILTER);
	}
  1. loggerContext标记为没有初始化
  2. 删除 Jdk Logging Bridge 处理程序
  3. 清除状态管理器
  4. 删除早期加入的过滤器

遗留问题

为什么日志系统初始化了两次?bootstrapContext

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是目前非常流行的Java Web应用开发框架之一,其内置的日志框架Logback也是非常重要的一部分。在码分析方面,我们可以通过阅读Spring Boot和Logback的官方文档以及相关的开代码库来深入了解其实现机制和使用方法。 在Logback中,主要的核心组件包括Logger、Appender和Layout。Logger是日志记录器,用于记录应用程序的日志信息;Appender是日志输出器,将日志信息输出到指定的地方,比如控制台、文件、数据库等;Layout是日志格式化器,用于将日志信息格式化成指定的输出格式。在Logback中,这三个组件是通过配置文件来进行管理和配置的。 而在Spring Boot中,Logback的使用也是非常简单的。一般来说,我们只需要在配置文件中加入一些特定的配置项,如logging.file指定日志输出到文件中,logging.level指定日志输出的级别等等。对于部分自定义需求,我们可以通过配置Logback的默认配置文件logback-spring.xml或者logback.xml来自定义相关的配置项,如修改日志格式、添加Appender等等。 在码分析方面,我们可以先从Spring Boot和Logback自身的码入手,理解其设计思想和实现机制。同时,可以通过开社区中的相关代码库,如spring-boot-starter-logging、logback等来深入研究它们的具体实现方法和使用技巧。最终,我们可以通过结合实际项目的应用场景,加深对Spring Boot和Logback的理解和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值