IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> logback源码阅读(三) springboot对LoggingSystem日志系统的支持 -> 正文阅读

[Java知识库]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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:29:56  更:2022-08-06 10:33:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:15:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码