日志的加载
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" };
}
其默认支持配置
总结
本篇只是简单的介绍了下日志系统的大概,日志的初始化,日志级别的变更都没有深入学习,有兴趣的可以后续找些资料学习。个人只看了工作中可能相关的一些内容。