日志系统种类
springboot通过slf4j门面支持多种日志系统:主要包含
- JavaLoggingSystem
- LogbackLoggingSystem
-
Log4j2LoggingSystem
详情参考下图:
日志系统初始化
spring.factories
日志系统的初始化主要是通过LoggingApplicationListener类来实现,详细代码见springboot依赖中META-INF下的spring.factories文件
# Application Listeners
org.springframework.context.ApplicationListener=\
...
org.springframework.boot.context.logging.LoggingApplicationListener,\
...
LoggingApplicationListener初始化日志系统
其中有若干方法,完成了
- 监听Event事件, 注册日志系统bean,初始化日志系统
- 日志系统初始化(initialize, initializeSystem),
- 初始化日志级别(initializeLogLevel, setLogLevels)
核心获取日志系统的代码如下:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
// 通过LoggingSystem获取对应的LoggingSystem(LogBackLoggingSystem/Log4j2LoggingSystem/JavaLoggingSystem/NoOpLoggingSystem)
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
this.initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
LoggingSystem获取日志系统
如果通过系统参数指定了日志系统,那么使用指定的日志系统;否则遍历静态常量SYSTEMS中的值,获取已存在的日志类对应的日志系统的第一个
public static LoggingSystem get(ClassLoader classLoader) {
// SYSTEM_PROPERTY = "LoggingSystem"
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
// 如果指定了LoggingSystem,就用已有配置,可以用来自定义日志系统
return (LoggingSystem)("none".equals(loggingSystem) ? new LoggingSystem.NoOpLoggingSystem() : get(classLoader, loggingSystem));
} else {
// 否则遍历SYSTEMS中已存在的日志系统的第一个
return (LoggingSystem)SYSTEMS.entrySet().stream().filter((entry) -> {
// 查看日志系统的类是否存在
return ClassUtils.isPresent((String)entry.getKey(), classLoader);
}).map((entry) -> {
// 获取日志系统
return get(classLoader, (String)entry.getValue());
// 取得第一个
}).findFirst().orElseThrow(() -> {
return new IllegalStateException("No suitable logging system located");
});
}
}
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);
}
从图中可以看出遍历会首先寻找LogbackLoggingSystem,然后是Log4j2LoggingSystem,最后是JavaLoggingSystem,如果系统有某个日志系统相关类,那么这个日志系统就会被初始化为应用的日志系统,下面具体的初始化工作交给具体日志系统
具体的日志系统初始化(Log4j2LoggingSystem为例)
Log4J2LoggingSystem继承自Slf4JLoggingSystem, Slf4JLoggingSystem继承自AbstractLoggingSystem,AbstractLoggingSystem继承LoggingSystem,从父类往子类看
- LoggingSystem是一个抽象类,主要定义了initialize(),getLoggerConfigurations(),setLogLevel()等方法让子类实现
- AbstractLoggingSystem抽象类,提供初始化initialize()实现(配置文件,默认),findConfig()寻找配置文件,和新的抽象方法loadDefaults(),loadConfiguration(),这两个方法交于子类实现加载日志配置
- Slf4JLoggingSystem抽象类,configureJdkLoggingBridgeHandler()主要配置SLF4JBridgeHandler相关(这块不是很清楚)
- Log4J2LoggingSystem核心类,完成loadDefaults(),loadConfiguration()日志配置,设置日志级别等工作
下面详细解释Log4J2LoggingSystem代码:
日志系统初始化
- beforeInitialize()主要实现Slf4JLoggingSystem的SLF4JBridgeHandler初始化
- initialize()主要从AbstractLoggingSystem查找初始化的方式(配置文件(包含xml文件和自定义配置),还是默认方式)
- reinitialize()通过LoggerContext重新加载配置URL
// 初始化前的准备工作,调用父类beforeInitialize方法,调用到AbstractLoggingSystem结束
// AbstractLoggingSystem中是个空方法
public void beforeInitialize() {
LoggerContext loggerContext = this.getLoggerContext();
if (!this.isAlreadyInitialized(loggerContext)) {
super.beforeInitialize();
loggerContext.getConfiguration().addFilter(FILTER);
}
}
// 初始化调用父类的initialize方法,进入AbstractLoggingSystem的initialize方法
// 这里FILTER是AbstractFilter,不理解是什么作用
/**
* Log4J2LoggingSystem代码片段
*/
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile){
LoggerContext loggerContext = this.getLoggerContext();
if (!this.isAlreadyInitialized(loggerContext)) {
loggerContext.getConfiguration().removeFilter(FILTER);
super.initialize(initializationContext, configLocation, logFile);
this.markAsInitialized(loggerContext);
}
}
/**
* AbstractLoggingSystem代码片段
*/
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile){
// 有配置项,使用配置项
if (StringUtils.hasLength(configLocation)) {
this.initializeWithSpecificConfig(initializationContext, configLocation, logFile);
} else {
// 否则使用默认的
this.initializeWithConventions(initializationContext, logFile);
}
}
/**
* Log4J2LoggingSystem代码片段
* 调用LoggerContext刷新配置
*/
protected void reinitialize(LoggingInitializationContext initializationContext) {
this.getLoggerContext().reconfigure();
}
日志系统加载配置项
- loadDefaults: logFile不为空,从org.springframework.boot.logging依赖的classpath下找log4j2-file.xml;否则从org.springframework.boot.logging依赖的classpath下找log4j2.xml,loadDefaults具体实现交给loadConfiguration
- loadConfiguration: 初始化LoggingSystem一些列日志重要参数,调用自己loadConfiguration初始化配置
// loadDefaults加载日志核心配置,具体实现交给loadConfiguration
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
if (logFile != null) {
this.loadConfiguration(this.getPackagedConfigFile("log4j2-file.xml"), logFile);
} else {
this.loadConfiguration(this.getPackagedConfigFile("log4j2.xml"), logFile);
}
}
// 下面两个loadConfiguration都是核心配置
/**
* Log4J2LoggingSystem代码片段
*/
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) {
// 设置LoggingSystem核心参数
super.loadConfiguration(initializationContext, location, logFile);
// 设置Log4j2核心参数
this.loadConfiguration(location, logFile);
}
/**
* LoggingSystem代码片段(核心参数)
*/
public void apply(LogFile logFile) {
PropertyResolver resolver = this.getPropertyResolver();
this.setSystemProperty(resolver, "LOG_EXCEPTION_CONVERSION_WORD", "exception-conversion-word");
this.setSystemProperty("PID", (new ApplicationPid()).toString());
this.setSystemProperty(resolver, "CONSOLE_LOG_PATTERN", "pattern.console");
this.setSystemProperty(resolver, "FILE_LOG_PATTERN", "pattern.file");
this.setSystemProperty(resolver, "LOG_FILE_MAX_HISTORY", "file.max-history");
this.setSystemProperty(resolver, "LOG_FILE_MAX_SIZE", "file.max-size");
this.setSystemProperty(resolver, "LOG_LEVEL_PATTERN", "pattern.level");
this.setSystemProperty(resolver, "LOG_DATEFORMAT_PATTERN", "pattern.dateformat");
if (logFile != null) {
logFile.applyToSystemProperties();
}
}
/**
* Log4J2LoggingSystem代码片段(核心参数)
*/
protected void loadConfiguration(String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
try {
// 获取日志上下文
LoggerContext ctx = this.getLoggerContext();
// 初始化配置URL路径
URL url = ResourceUtils.getURL(location);
// 构造ConfigurationSource 对象
ConfigurationSource source = this.getConfigurationSource(url);
// 核心!!!!!!!!!!!!,日志上下文加锁,设置日志配置项
ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
} catch (Exception var6) {
throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, var6);
}
}
后话
至此一个以Log4j2为日志系统的LoggingSystem核心配置初始化完毕,当然由于理解了源码,就会有其他的玩法:通过springboot配置中心动态修改日志系统的配置
核心就是:自定义一个日志系统(比如继承自Log4j2LoggingSystem):重写loadDefaults和loadConfiguration方法,使得日志配置项(JSON或者XML)可以从配置中心动态获取,生产出现bug如果日志不能定位出问题,可以临时修改日志配置项,定位问题。