logback源码阅读(一)获取ILoggerFactory、LoggerContextInitializer.autoConfig()的findURLOfDefaultConfigurationFile方法中,我们知道默认配置配置文件是依次按照logback.configurationFile,logback-test.xml,logback.xml得到。但是很多项目中是这么配置的。
logging:
config: classpath:logback-ae.xml
- 这样配置的优势是什么?由于标准的logback.xml配置文件加载的太早,所以你不能在里面使用扩展部分。你需要使用logback-spring.xml或者通过logging.config自定义比如读取系统变量等
- 这样配置是怎么加载的配置文件?
回答第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
- LoggingApplicationListener是配置 LoggingSystem 的 ApplicationListener。
- 如果环境包含 logging.config 属性,它将用于引导日志系统,否则使用默认配置。
- 无论如何,如果环境包含 logging.level,则日志级别将被自定义。条目和日志记录组可以使用 logging.group 定义。
- 默认情况下,日志输出仅写入控制台。如果需要日志文件,可以使用 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);
String logConfig = environment.getProperty(CONFIG_PROPERTY);
if (ignoreLogConfig(logConfig)) {
system.initialize(initializationContext, null, logFile);
}
else {
try {
ResourceUtils.getURL(logConfig).openStream().close();
system.initialize(initializationContext, logConfig, logFile);
}
catch (Exception ex) {
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);
}
- loggerContext标记为没有初始化
- 删除 Jdk Logging Bridge 处理程序
- 清除状态管理器
- 删除早期加入的过滤器
遗留问题
为什么日志系统初始化了两次?bootstrapContext
|