日志的加载

LoggingApplicationListener 监听器

ps.这篇是我最早写的,所以当时还是习惯把ApplicationContext说成容器

支持的事件类型

supportsEventType

public boolean supportsEventType(ResolvableType resolvableType) {
		// 判断事件类型是否支持,所支持的事件类型在EVENT_TYPES数组中维护
		return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
	}
	
	private boolean isAssignableFrom(Class<?> type, Class<?>... supportedTypes) {
		if (type != null) {
			for (Class<?> supportedType : supportedTypes) {
				if (supportedType.isAssignableFrom(type)) {
					return true;
				}
			}
		}
		return false;
	}

根据代码主要处理EVENT_TYPES中的类,而EVENT_TYPES内容有

private static final Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
			ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class,
			ContextClosedEvent.class, ApplicationFailedEvent.class };

支持的数据源

supportsSourceType

public boolean supportsSourceType(Class<?> sourceType) {
		return isAssignableFrom(sourceType, SOURCE_TYPES);
	}

根据代码主要处理SOURCE_TYPES中的类,而SOURCE_TYPES内容有

private static final Class<?>[] SOURCE_TYPES = { SpringApplication.class,
			ApplicationContext.class };

事件处理

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

事件处理 - 容器启动的时的事件

onApplicationStartingEvent

/**
	 * 容器启动的时的事件
	 * @param event
	 */
	private void onApplicationStartingEvent(ApplicationStartingEvent event) {
		// 获得loggingSystem对象
		this.loggingSystem = LoggingSystem
				.get(event.getSpringApplication().getClassLoader());
		// 进行日志初始化
		this.loggingSystem.beforeInitialize();
	}

在容器启动的时候,根据容器的类加载器,初始化日志系统,然后执行日志系统的第一步初始化。

事件处理 - 容器的环境准备完成

onApplicationEnvironmentPreparedEvent

/**
	 * 容器的环境准备完成
	 * @param event
	 */
	private void onApplicationEnvironmentPreparedEvent(
			ApplicationEnvironmentPreparedEvent event) {
		if (this.loggingSystem == null) {
			this.loggingSystem = LoggingSystem
					.get(event.getSpringApplication().getClassLoader());
		}
		// 进行初始化
		initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
	}
	
	protected void initialize(ConfigurableEnvironment environment,
			ClassLoader classLoader) {
		// 将loggingSystem绑定到容器的环境中
		new LoggingSystemProperties(environment).apply();
		// 获得logFile
		LogFile logFile = LogFile.get(environment);
		if (logFile != null) {
			logFile.applyToSystemProperties();
		}
		// 初始化日志级别
		initializeEarlyLoggingLevel(environment);
		// 初始化loggingSystem
		initializeSystem(environment, this.loggingSystem, logFile);
		// 初始化最终的日志级别
		initializeFinalLoggingLevels(environment, this.loggingSystem);
		// 注册ShutdownHook 
		registerShutdownHookIfNecessary(environment, this.loggingSystem);
	}

在容器环境准备完成后,日志系统就可以读取环境中的数据进行初始化操作了。

事件处理 - 容器准备完成

onApplicationPreparedEvent

/**
	 * 容器准备完成
	 * @param event
	 */
	private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
		ConfigurableListableBeanFactory beanFactory = event.getApplicationContext()
				.getBeanFactory();
		if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
			// 将日志对象loggingSystem注册到bean工厂中
			beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
		}
	}

容器准备完成后,可以接受bean的注册了,此时可以将日志系统注册到bean工厂中。

事件处理 - 容器已经关闭

onContextClosedEvent

/**
	 * 容器已经关闭
	 */
	private void onContextClosedEvent() {
		if (this.loggingSystem != null) {
			// loggingSystem 进行清空
			this.loggingSystem.cleanUp();
		}
	}

容器关闭后,清空日志框架的相关数据。

事件处理 - 容器启动失败

onApplicationFailedEvent

private void onApplicationFailedEvent() {
		if (this.loggingSystem != null) {
			// loggingSystem 进行清空操作
			this.loggingSystem.cleanUp();
		}
	}

类似容器关闭,容器启动失败一样清空数据

日志框架

LoggingSystem 是springboot的日志系统的抽象,提供不同日志系统的实现类

它的主要方法包含

  • beforeInitialize:初始化的前置方法,抽象由子类实现
  • initialize:初始化方法,空实现,子类实现
  • cleanUp:清空,空实现,子类实现
  • getShutdownHandler:获得钩子的Runnable对象,默认返回null
  • getSupportedLogLevels:将LogLevel日志等级的枚举类转换为set
  • setLogLevel:设置日志级别,未实现异常
  • getLoggerConfigurations,getLoggerConfiguration: 都是获得配置的方法,未实现异常
  • get:两种get都是获得日志系统框架实现类的方法

get 获得日志系统的实现类

public static LoggingSystem get(ClassLoader classLoader) {
		// 通过LoggingSystem的类名获得系统中参数
		String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
		// 如果有值,标识有相关配置
		if (StringUtils.hasLength(loggingSystem)) {
			// 如果是none则返回NoOpLoggingSystem类型对象
			if (NONE.equals(loggingSystem)) {
				return new NoOpLoggingSystem();
			}
			// 否则根据loggingSystem获得对应的类对象
			return get(classLoader, loggingSystem);
		}
		// 如果不存在就从SYSTEMS的配置中依次获取存在的类,
		// 返回第一个被匹配的,假如没有则抛出异常
		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"));
	}

这个SYSTEMS是一个map包含一些默认设置的日志系统:

  • ch.qos.logback.core.Appender
  • org.apache.logging.log4j.core.impl.Log4jContextFactory
  • java.util.logging.LogManager

另外一个get方法get(ClassLoader classLoader, String loggingSystemClass)内容就是根据类加载器加载指定名称(loggingSystemClass)的类。

日志系统配置

LoggingSystemProperties

在LoggingApplicationListener对ApplicationEnvironmentPreparedEvent事件进行处理中调用了一个方法initialize,方法中第一步就执行了

// 将loggingSystem绑定到容器的环境中
new LoggingSystemProperties(environment).apply();

而这个LoggingSystemProperties就是日志的默认配置

apply

将默认配置配置到系统中。其中resolver指的就是容器环境(environment)或者其属性源(PropertySources)

/**
	 * 配置日志配置到容器环境中
	 * @param logFile
	 */
	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();
		}
	}

日志文件对象

LogFile 是LoggingSystemProperties#apply的参数,是springboot对日志输出文件的描述

/**
	 * 获得 文件名和文件路径
	 * 根据文件名和文件路径获得LogFile对象
	 */
	public static LogFile get(PropertyResolver propertyResolver) {
		String file = propertyResolver.getProperty(FILE_PROPERTY);
		String path = propertyResolver.getProperty(PATH_PROPERTY);
		if (StringUtils.hasLength(file) || StringUtils.hasLength(path)) {
			return new LogFile(file, path);
		}
		return null;
	}

日志框架的实现类

springboot为不同的日志系统提供了不同的实现类。

类图关系

NoOpLoggingSystem

LoggingSystem的内部类,所有方法空实现,禁用日志系统

/**
	 * LoggingSystem的空实现类,用于禁用日志系统
	 */
	static class NoOpLoggingSystem extends LoggingSystem {
		@Override
		public void beforeInitialize() {
		}

		@Override
		public void setLogLevel(String loggerName, LogLevel level) {
		}

		@Override
		public List<LoggerConfiguration> getLoggerConfigurations() {
			return Collections.emptyList();
		}

		@Override
		public LoggerConfiguration getLoggerConfiguration(String loggerName) {
			return null;
		}

	}

AbstractLoggingSystem 的实现类

日志系统的抽象实现类,基类。主要实现了初始化操作的方法。

@Override
	public void initialize(LoggingInitializationContext initializationContext,
			String configLocation, LogFile logFile) {
		// 有配置则使用配置进行初始化
		if (StringUtils.hasLength(configLocation)) {
			initializeWithSpecificConfig(initializationContext, configLocation, logFile);
			return;
		}
		// 无配置则使用默认规则
		initializeWithConventions(initializationContext, logFile);
	}
	
	private void initializeWithConventions(
			LoggingInitializationContext initializationContext, LogFile logFile) {
		// 获得约定配置
		String config = getSelfInitializationConfig();
		// 如果获取到且结果 logFile 为空,则重新初始化
		if (config != null && logFile == null) {
			// self initialization has occurred, reinitialize in case of property changes
			reinitialize(initializationContext);
			return;
		}
		// 如果获取不则使用约定配置 后面添加了-spring.
		if (config == null) {
			config = getSpringInitializationConfig();
		}
		// 如果获取到,则加载配置文件
		if (config != null) {
			loadConfiguration(initializationContext, config, logFile);
			return;
		}
		// 否则使用默认策略
		loadDefaults(initializationContext, logFile);
	}

目前默认配置loadDefaults为空,需要子类实现。

LogbackLoggingSystem

LoggingSystem 的实现类,继承于Slf4JLoggingSystem,基于Logback 的日志实现类

getStandardConfigLocations

这里保存了日志系统约定好的配置文件

protected String[] getStandardConfigLocations() {
		// 约定好的日志配置地址
		return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
				"logback.xml" };
	}

关于这个默认配置的调用,在AbstractLoggingSystem基础抽象类的初始化方法initializeWithConventions中有一段代码

// 获得约定配置
String config = getSelfInitializationConfig();

getSelfInitializationConfig

protected String getSelfInitializationConfig() {
		return findConfig(getStandardConfigLocations());
	}

这个时候调用的getStandardConfigLocations就是子类实现的同名方法。

Log4J2LoggingSystem

基于 Log4J2 的 LoggingSystem 实现类

private String[] getCurrentlySupportedConfigLocations() {
		List<String> supportedConfigLocations = new ArrayList<>();
		if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) {
			Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml");
		}
		if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
			Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn");
		}
		supportedConfigLocations.add("log4j2.xml");
		return StringUtils.toStringArray(supportedConfigLocations);
	}

可以看到针对log4j2不仅支持xml还提供了的yml的支持。

JavaLoggingSystem

基于 JUL 的 LoggingSystem 实现类

protected String[] getStandardConfigLocations() {
		return new String[] { "logging.properties" };
	}

其默认支持配置

总结

本篇只是简单的介绍了下日志系统的大概,日志的初始化,日志级别的变更都没有深入学习,有兴趣的可以后续找些资料学习。个人只看了工作中可能相关的一些内容。