SpringBoot日志源碼解析:日志監(jiān)聽器的執(zhí)行
LoggingApplicationListener 的執(zhí)行
LoggingApplicationListener 的主要作用是配置LoggingSystem, 如果 環(huán)境 包含 loggingconfig 屬性,LoggingApplicationListener 將用于引導 日志記錄系統(tǒng),否則使用默認配置。
如果環(huán)境包含 logging.level.*和日志記錄組,則可以使用 logging.group 定義日志記錄級別。
關(guān)于 LoggingApplicationL istener 的重點功能我們后面章節(jié)再進行講解。
LoggingApplicationListener 實現(xiàn)自 GenericApplicationListener 接口,具有監(jiān)聽器的特性。
因此,執(zhí)行
EventPublishingRunListener 廣播事件之后,LoggingApplicationListener 便會監(jiān)聽到對應(yīng)的事件并執(zhí)行 onApplicationEvent 方法中的邏輯判斷,有針對性地處理不同的事件,相關(guān)代碼如下。
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
// springboot 啟動時觸發(fā)
onApplicationStartingEvent( (ApplicationStartingEvent) event);
} else if (event instanceof ApplicationEnvironmentPreparedEvent) {
// Environment 環(huán)境準備初級階段觸發(fā)
onApplicationEnvironmentPreparedEvent( (Applicat ionEnvironmentPrepared-
Event) event);
} else if (event instanceof ApplicationPreparedEvent) {//應(yīng)用上下文準備完成,但未刷新時觸發(fā)
onApplicationPreparedEvent( (Applicat ionPreparedEvent) event);
else if (event instanceof ContextClosedEvent
&& ((ContextClosedEvent) event). getApplicationContext() . getPar
ent() == null) {
//容器關(guān)閉時處理
onContextClosedEvent( );
} else if (event instanceof ApplicationFailedEvent) {
//啟動失敗時處理
onApplicationFailedEvent();
}
}以上代碼中的事件處理基本涵蓋了 Spring Boot 啟動的不同階段和不同狀況,比如 SpringBoot 剛剛啟動階段、環(huán)境準備初級階段、應(yīng)用上下文準備完成階段、容器關(guān)閉階段、應(yīng)用程序啟動失敗等。后面章節(jié)我們會對這些過程中日志系統(tǒng)是如何處理的進行詳解介紹。

ApplicationStartingEvent 事件處理
在 Spring Boot 的啟動過程中,通過
SpringApplicationRunListeners 類間接的調(diào)用了EventPublishingRunListener 中 的 各 類 事 件 的 發(fā) 布 方 法 , 最 終 被 LoggingApplicationListener 監(jiān)聽并進行處理。在后續(xù)的講解中,我們省略這個中間調(diào)用過程,直接講解 Logging-ApplicationL istener 接收到事件后的處理。
Spring Boot 剛 剛 啟 動 時 發(fā) 布 了 ApplicationStartingEvent 事 件 ,
LoggingApplication-Listener 中的 onApplicationStartingEvent 方法便被調(diào)用了,該方法源碼如下。
private void onApplicat ionStartingEvent(ApplicationStartingEvent event)
this .loggingSystem = LoggingSystem. get(event . getSpringApplication(). getCl
assLoader()) ;
this. loggingSystem. beforeInitialize();
}在
onApplicationStartingEvent 方法中,首先獲得一個 LoggingSystem 對象,然后調(diào)用對象的 beforelnitialize 方 法進行預(yù)初始化操作。也就是說在 Spring Boot 開始啟動時,日志系統(tǒng)做了兩件事:創(chuàng)建 LoggingSystem 對象和預(yù)初始化操作。
LoggingSystem 為日志系統(tǒng)的通用抽象類,其中也提供了獲取 LoggingSystem 對象的靜態(tài)方法。上面 LoggingSystem 的創(chuàng)建便 是調(diào)用其 get 方法獲得,相關(guān)代碼如下。
public static LoggingSystem get(ClassLoader classLoader) {
//從系統(tǒng)變量中獲得 L oggingSystem 的類名String loggingSystem = System. getProperty(SYSTEM PROPERTY);
f (StringUtils . hasLength(loggingSystem)) {
if (NONE . equals(loggingSystem))
return new NoOpLoggingSystem();
/如果存在,則通過反射進行對象的初始化
return get(classLoader, loggingSystem);
// MSYSTEMS 篩選并初始化 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"));
}該方法首先判斷系統(tǒng)中是否配置了 LoggingSystem 的配置,存在且不為“none”時,則利用反射機制進行初始化;如果明確配置為"none”,則返回 NoOpL oggingSystem 對象。實例化配置的 L oggingSystem 相關(guān)代碼如下。
private static LoggingSystem get(ClassLoader classLoader, String loggingSys
temClass) {
try
Class> systemClass = ClassUtils . forName( loggingSystemClass, classLoad
er);
Constructor> constructor = systemClass . getDeclaredConstructor(Class-
Loader.
class);
constructor . setAccessible(true);
return (LoggingSystem) constructor. newInstance(classLoader);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}以上代碼就是通過獲取指定類的構(gòu)造器,調(diào)用其 newInstance 方法來創(chuàng)建 LoggingSystem對象的。
如果系統(tǒng)中不存在該對象的配置,則從 SYSTEMS 篩選獲取第一個符合條件的值,然后進行初始化。SYSTEMS 為 L oggingSystem 的靜態(tài)變量,通過靜態(tài)代碼塊進行初始化,相關(guān)代碼如下。
private static final Map<String, String> SYSTEMS;static {
Map<String, String> systems = new LinkedHashMap<>();
systems . put("ch. qos . logback. core . Appender"
"org. springframework . boot . logging . logback. LogbackLoggingSyste
m");
systems . put("org. apache. logging. log4j . core. impl. Log4jContextFactory"
"org. springframework. boot. logging . log4j2 . Log4J2LoggingSyste
m");
systems . put("java .util. logging. LogManager" ,
"org. springframework . boot . logging . java . Javal oggingSystem");
SYSTEMS = Collections . unmodifiableMap(systems);
}常 量 SYSTEMS 是 Map 結(jié) 構(gòu) , 其 中 key 為 對 應(yīng) 日 志 系 統(tǒng) 的 核 心 類 ( 類 似@ConditionalOn-Class 注解中指定的類), value 的值 是 LoggingSystem 的具體實現(xiàn)類。
在靜態(tài)代碼塊中,初始化分別添加了 LogbackL oggingSystem、Log4J2L oggingSystem 和JavaLoggingSystem,這也是 Spring Boot 默認內(nèi)置的 3 個日志實現(xiàn)類。而且 SYSTEMS 被初始化之后便不可被修改了。
其中從 SYSTEMS 中篩選出符合條件的 L oggingSystem 實現(xiàn)類,這里采用了 Java 8 新增的 Stream 語法來實現(xiàn),基本處理過程是這樣的:遍歷 SYSTEMS 中的值,通過 ClassUtils的 isPresent 方 法過濾符合條件的值(key 對應(yīng)的類存在于 classpath 中) ;然后通過上面提到的反射方法創(chuàng)建篩選過后的值的對象;最后獲取第一個對象并返回。如果未獲取到則拋出異常。
由于 SYSTEMS 是基于 LinkedHashMap 實現(xiàn)的,因此,這里可以看出默認情下 SpringBoot優(yōu)先采用
org.springframework.bot.logging.logback.LogbackL oggingSystem 實現(xiàn)類。
也就是說,默認情況下使用 L ogback 進行日志配置。
完成 LoggingSystem 初始化之后,程序便調(diào)用其 beforelnitialize 方法進行初始化前的準備工作。在 L oggingSystem 中 beforelnitialize 為抽象方法,由子類實現(xiàn)。該方法在 LogbackLoggingSystem 中的源碼實現(xiàn)如下。
public void beforeInitialize() {
//獲得 LoggerContext
LoggerContext loggerContext = getLoggerContext();
//如果 LoggerContext 中已經(jīng)配置有 oggingSystem 對應(yīng)的 Logger,則直接返回
if (isAlreadyInitialized(loggerContext)) {
return;
11 調(diào)用父類的初始化方法
super . beforeInitialize();//向 LoggerContext 中的 TurboFilterL ist 添加 1 個 TurboFilter
// 目的是在 L oggerContext 沒有初始化之前對應(yīng)打印的日志的請求全部拒絕
loggerContext . getTurboFilterList() . add(FILTER);
}該方法的主要功能是獲得 LoggerContext 并校驗是否存在對應(yīng)的 logger,如果不存在則調(diào)用父類的初始化方法,并拒絕在 L oggerContext 沒有初始化之前對應(yīng)打印的日志的全部請求。
LogbackL oggingSystem 的 父 類 為 SIf4JL oggingSystem, 因 此 方 法 中 調(diào) 用 了 SIf4JLogging-System 的 beforelnitialize 方法,相關(guān)源碼如下。
public abstract class Slf4JLoggingSystem extends AbstractLoggingSystem {
private static final String BRIDGE_ HANDLER = "org. slf4j . bridge . SLF4JBridg
eHandler";
@Override
publi
ic void beforeInitialize()
//調(diào)用父類的 beforeInitialize, 默認父類實現(xiàn)為空
super . beforeInitialize();
//配置 jdk 內(nèi)置日志與 SLF4J 直接的嬌接 Handler
configureJdkL oggingBridgeHandler();
private void configureJdkl oggingBridgeHandler() {
//判斷是否需要將 JUL 橋接為 SLf4j
if (isBridgeJulIntoSlf4j()) {
//刪除 jdk 內(nèi)置日志的 Handler
removeJdk oggingBridgeHandler();
//添加 SLF4J 的 Handler
SLF4JBridgeHandler . install();
catch (Throwable ex) {
//忽略異常。沒有 java. util. Logging 橋接被安裝
}
//根據(jù)兩個條件判斷是否將 JUL 橋接為 SLF4J
protected final boolean isBridgeJulIntoSlf4j() {
return isBridgeHandlerAvailable() && isJulUsingASingleConsoleHandler-
AtMost();
/判斷 org. slf4j. bridge. SLF4JBridgeHandler 是否存在于類路徑下
protected final boolean isBridgeHandlerAvailable()
return ClassUtils. isPresent(BRIDGE_ HANDLER, getClassLoader());//判斷是否不存在 HandLer 或只存在-個 Consol eHandler
private boolean isJulUsingASingleConsoleHandlerAtMost() {
Logger rootLogger = LogManager . getLogManager(). getLogger("");
Handler[] handlers = rootlogger. getHandlers();
return handlers.length == 0 | | (handlers.length == 1 && handlers[0] ins
tanceof-
ConsoleHandler);
//移除 Handler
private void removeJdkL oggingBridgeHandler() {
try {
removeDefaultRootHandler();
//移除 SLF4J 相關(guān) Handler
SLF4JBridgeHandler . uninstall();
} catch (Throwable ex) {
//忽略并繼續(xù)
//移除 ConsoleHandler
private void removeDefaultRootHandler() {
try {
Logger rootLogger = LogManager . getLogManager().getLogger(" );
Handler[] handlers = rootl ogger. getHandlers( );
if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) {
rootLogger . removeHandler(handlers[0]);
} catch (Throwable ex) {
//忽略并繼續(xù)
}
}
}上述代碼涵蓋了 SIf4JL oggingSystem 中大多數(shù)的功能,其主要目的就是處理內(nèi)置日志(JUL)與 SLF4J 的 Handler 的橋接轉(zhuǎn)換操作。
基本判斷邏輯如下:如果類路徑下存在 SLF4JBridgeHandler 類,并且根 Logger 中不包含或僅包含 ConsoleHandler 時,說明需要將內(nèi)置日志轉(zhuǎn)換為 SLF4J。
基本轉(zhuǎn)換過程分兩步:刪除原有 Handler、新增指定的 Handler; 如果滿足條件,先刪除內(nèi)置日志的Handler,然后再刪除SLF4J的Handler,最后再將SLF4J對應(yīng)的SLF4JBridgeHandler添加到根 L ogger 中。
具體的方法實現(xiàn)參考上述代碼中的注解,關(guān)于 SLF4J 的 uninstall 方法和 install 方法均在SLF4JBridgeHandler 類中,實現(xiàn)比較簡單,在此不再進行拓展。不過建議讀者朋友閱讀一下 SLF4JBridgeHandler 中的源代碼, 其內(nèi)部還提供了轉(zhuǎn)換過程中各層級日志級別對應(yīng)等處理。
至此,針對 Spring Boot 啟動階段發(fā)出的 ApplicationStartingEvent 事件及日志系統(tǒng)所做的相應(yīng)操作已經(jīng)講解完畢。

ApplicationEnvironmentPreparedEvent 事件處理
當 SpringBoot 繼續(xù)啟動操作便會廣播
ApplicationEnvironmentPreparedEvent 事件,此時便會調(diào)用 LoggingApplicationListener 的 onApplicationEnvironmentPreparedEvent 方法,該方法源碼如下。
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null)
this. loggingSystem = LoggingSystem
.get(event
getSpringApplication()
initialize(event. getEnvironment(), event . getSpringApplication(). getClassL
oader());
}在上一節(jié)中,ApplicationStartingEvent 事件觸發(fā)時 ,loggingSystem 已經(jīng)初始化賦值了,在該方法中會再次判斷l(xiāng)oggingSystem是否為null,如果為null,則通過LoggingSystem的 get方法進行對象創(chuàng)建。
完成 loggingSystem 的再次判斷并創(chuàng)建之后,調(diào)用 initialize 方法進行初始化操作,主要完成了初始參數(shù)的設(shè)置、日志文件、日志級別設(shè)置以及注冊 ShutdownHook 等操作,相關(guān)代碼如下。
protected void initialize(ConfigurableEnvironment environment, ClassLoader
classLoader) {
//創(chuàng)建 LoggingSystemProperties 對象,并 沒置默認屬性
new LoggingSystemProperties( environment) . apply();
//獲取 LogFile,如果 LogFile 存在, 則向系統(tǒng)屬性寫入 LogFile 配置的文件路徑
this.logFile = LogFile . get( environment);
if (this.logFile != null) {
this . logFile . applyToSystemProperties();
this. loggerGroups = new LoggerGroups(DEFAULT_ GROUP_ LOGGERS);
!1 rsrinooogging
initializeEarlyLoggingL evel(environment);
//初始化 L oggingSysteminitializeSystem(environment, this . loggingSystem, this. logFile);
//最終沒置日志級別
initializeFinaloggingL evels(environment, this . loggingSystem) ;
//注冊 ShutdownHook
registerShutdownHookIfNecessary(environment, this . loggingSystem);
}上述代碼中,創(chuàng)建 LoggingSystemProperties 對象之 后主要是通過調(diào)用其 apply 方法來獲取默認的日志配置參數(shù)(在配置文件中以"logging.”開頭的屬性),并設(shè)置到系統(tǒng)屬性中。
LogFile的get方法主要是獲取日志文件的路徑和名稱,并作為參數(shù)創(chuàng)建Logfile對象。LogFile中 get 方法相關(guān)代碼如下。
public static LogFile get(PropertyResolver propertyResolver) {
Strinp file = petI OpFi ] ePronertv( nronertvResolver. FTI F NAMF PROPERTY. F T
LE_ PROPERTY);
String path = getLogFileProperty(propertyResolver, FILE_ PATH PROPERTY, PA
if (StringUtils.haslength(file) | StringUtils.haslength(path)) {
return new LogFile(file, path);
}
return null;
}從上述代碼可以看出,通過獲取屬性名為"logging.ile.path"的值得到了 日志的路徑,通過獲取屬性名為"logging.file.name "的值得到了日志文件名。其中 getLogFileProperty 的第 3 個參數(shù)為兼容歷史版本中的配置屬性名。當然,程序會優(yōu)先獲取當前版本的屬性配置,當查找不到值時才會獲取歷史版本的值。
緊接著,initialize 方法中判斷當 LogFile 不為 null 時,調(diào)用它的 apply ToSystemProperties方法,也就是將上述獲得的日志文件路徑和名稱存入系統(tǒng)屬性當中。
initializeEarlyL oggingl .evel 方法用于早期設(shè)置 springBootl ogging 的值和 LoggingSystem的初始化,代碼如下。
private void initializeEarlyLoggingl evel (Conf igurableEnvironment environmen
if (this. parseArgs && this . springBootLogging == null) {
f (isSet(environment, "debug")) {
this . springBootLogging = LogLevel. DEBUG;
this. springBootlogging = Loglevel. TRACE;
}
}
}上述代碼主要根據(jù) parseArgs 參數(shù)(默認為 true )和 springBootLogging 是否為 null,在早期階段中設(shè)置 springBootL ogging 的值,也就是日志級別。
在 parseArgs 為 true, 并 且 springBootLogging 值 為 null 的 情 況 下 , 如 果Configurable-Environment 中 debug 的值存在且為 true, 則設(shè)置 springBootL ogging 為DEBUG。同樣,如果 trace 的值存在且為 true,則設(shè)置 springBootLogging 為 TRACE。
初始化 LoggingSystem 的代碼如下。
private void initializeSystem(Conf igurableEnvironment environment,
LoggingSystem system, LogFile logFile) {
//實例化 LoggingInitial izat ionContext
LoggingInitializationContext initializationContext = new LoggingInitializ
ationContext (
environment);
String 1ogConfig 二 enviroment .getProperty(CONFITG PROPERTY);
if (ignoreLogConfig(logConfig))
//如果 logging. config 沒有配置或者配置的值是 D 開頭的,則調(diào)用 L oggingSystem
的方法進行初始化
system . initialize(initializationContext, null, logFile);
} else {
11 通過 ResourceUtils 進行加載判斷其文件是否存在,如果不存在,則拋出 Illega
lStateException
ResourceUtils .getURL(logConfig) .openStream().close();
//存在則調(diào)用 L oggingSystem 的方法進行實例化
system. initialize(initializationContext, logConfig, logFile);
} catch (Exception ex) {
private boolean ignoreLogConfig(String logConfig) {
return !StringUtils.hasLength(logConfig) 11 logConfig. startsWith("-D");
}initializeSystem 方法中代碼的邏輯主要是圍繞 L oggingSystem 的初始化來進行的,首先為其初始化準備了
LogginghitializationContext 對象。然后獲取 logging.config 的參數(shù)并賦值給 logConfig,如果 logConfig 未配置 或者配置的值以 D 開頭,則調(diào)用 LoggingSystem 的initialize 方法進行初始化; 其他情況則通過 ResourceUtil 加載判斷對應(yīng)配置文件是否存在,如果不存在,則拋出llegalStateException;如果存在,則同樣調(diào)用L oggingSystem的initialize方法進行初始化。
下面我們來看 LoggingSystem 的 itialize 方法的源代碼。該方法在 LoggingSystem 類中的實現(xiàn)為空,而在其子類 AbstractL oggingSystem 中提供了如下的實現(xiàn)。
@Overridepublic void initialize(LoggingInitializationContext initializationContext,
String configLocation, LogFile logFile) {
if (StringUtils . hasLength(configLocation))
initializeWi thSpecificConfig(initializationContext, configLocation, log
File);
return;
}
initializeWithConvent ions (initializationContext, logFile);
}.上述代碼中,如果用戶指定了配置文件,則加載指定配置文件中的屬性進行初始化操作;如果未指定配置,則加載默認的配置,比如 log4j2 的 log4j2 .properties 或 log4j2.xml。其中默認加載日志配置文件名稱及文件格式由具體的子類實現(xiàn)。
下面重點講解 SpringBoot 中默認查找配置文件路徑的實現(xiàn),該部分在 LoggingSystem的抽象子類 AbstractL oggingSystem 的 initializeWithConventions 中實現(xiàn)。
private void initializeWithConventions(
LoggingInitializationContext initializationContext, LogFile logFile)
String config = getSelfInitializat ionConfig();
if (config != null && logFile == null) {
//自我初始化操作,屬性變更時會重新初始化
reinitialize(initializationContext);
return;
}
if (config == null) {
config = getSpringInitializationConfig();
}
if (config != null) {
loadConfiguration(initializationContext, config, logFile);
return;
loadDefaults(initializationContext, logFile);
}該方法的基本流程是:首先,獲得默認的日志配置文件(比如 logback.xml 等), 當配置文件不為 null,且 logFile 為 null 時, 進行自我初始化,具體實現(xiàn)由不同的日志框架來執(zhí)行,主要就是重置數(shù)據(jù)并加載初始化;然后,如果默認的配置文件不存在,則嘗試獲取包含“-spring”的名稱的配置文件(比如 logback-spring.xmI 等),如果獲得對應(yīng)的配置文件,則直接加載初始化;最后,如果上述兩種類型的配置文件均未找到,則調(diào)用 loadDefaults 方法采用默認值進行加載配置。
getSelflnitializationConfig 方法和 getSpringlnitializationConfig 都是用來獲取默認配置文件名稱的,不同之處在于獲得的配置文件的名稱中是否多了“一 spring"。這兩個方法都是先調(diào)用 getStandardConfig ocations 方法獲得默認的配置文件名稱數(shù)組,然后再調(diào)用 findConfig來驗證獲取符合條件的值。
我們先看不同之處,
getSpringhnitializationConfig 方法通過 getSpringConfigL ocations 來獲得配置文件名稱數(shù)組。
protected String[] getSpringConfigLocations() {
//獲得默認配置文件的路徑
String[] locations = getStandardConfigLocations();
for (int i = 0; i < locations.length; i++) {
String extension = StringUtils. getFilenameExtens ion(locations[i]);
locations[i] = locations[i]. substring(0,
locations[i].length() - extensio
n.1ength() - 1) + "-spring.
t extension;
return locations;
}上述代碼中,通過
getStandardConfigLocations 獲得 了默認配置文件名稱數(shù)組,然后對路徑中的文件名進行兼容處理,比如默認配置文件名稱為 logback.xml ,當我們配置為logback-spring.xml 時 , 通 過 getSelfInitializationConfig 方 法 無 法 加 載 到 , 但 通 過getStandard-ConfigLocations 方法則可以加載到。上述方 法核心處理就是將默認的配置文件名截取之后拼接上了“-spring"。
其中兩個方法都調(diào)用了
getStandardConfigLocations 方法為 AbstractLoggingSystem 的抽象方法,具體實現(xiàn)由具體日志框架的子類來完成,比如在 L ogbackL oggingSystem 中,該方法的實現(xiàn)如下。
protected String[] getStandardConfigLocations() {
return new String[] { "logback- test . groovy", "logback-test . xml", "logbac
k. groovy"
"logback.xml" };
}也就是說,LogbackLoggingSystem 默認支持以 logback-test.groovy、logback-test.xml、logback.groovy、logback.xm 以及 上述名稱擴展了“-spring”(比如 logback-spring.xml)的配置文件。
無論是通過配置指定配置文件名稱,還是通過上述默認方式獲得配置文件名稱,當獲得之后,都會調(diào)用 loadConfiguration 方法進行配置的加載。loadConfiguration 方法由子類來實現(xiàn),比如,LogbackLoggingSystem 中實現(xiàn)的源碼如下。
@Override
protected void loadConfiguration(LoggingInitializat ionContext initializat
String location, LogFile logFile) {
super . loadConfiguration(initializationContext, location, logFile);
//獲得 LoggerContext, 這里實際上是 ILoggerFac tory 的具體實現(xiàn)
LoggerContext loggerContext = getLoggerContext();
stopAndReset(loggerContext);
configureByResourceUrl(initializationContext, loggerContext,
} catch (Exception ex) {
ResourceUtils.getURL(location));
}
}
}上述代碼中首先會調(diào)用父類的 loadConfiguration 方法,該方法的最終操作還是調(diào)用了前面講到的 LoggingSystemProperties#apply 方法進行參數(shù)的獲取,并設(shè)置到系統(tǒng)屬性中。
getL oggerContext 獲得了 LoggerContext 對象,本質(zhì)上 LoggerContext 是 ILoggerFactory的具體實現(xiàn)類。隨后通過 stopAndReset 方法對日志相關(guān)的參數(shù)、監(jiān)聽器等進行停止和重置。
configureByResourceUrl 方法重點實現(xiàn)了針對 xml 格式的配置文件和其他格式(比如 groovy后綴)的配置文件的解析和具體配置,相關(guān)操作由對應(yīng)的日志框架內(nèi)部提供的類來實現(xiàn)。
最 后 , 再 回 到 AbstractL
oggingSystemtinitializeWithConventions 方 法 中 調(diào) 用 的IloadDefaults 方法,看看當未查找到配置文件時是如何處理的。
loadDefaults 方法同樣是抽象方法,在 LogbackLoggingSystem 中的具體實現(xiàn)如下。
@Override
protected void loadDefaults(LoggingInitializationContext initializationCo
ntext, LogFile
//獲得 L oggerContext(ILoggerFactory)并進行重置操作
secrerecntxt BetLossercontext();
//獲得是否為 debug 模式
boolean debug = Boolean.getBoolean("logback. debug");
//如果為 debug 模式則添加控制臺監(jiān)聽器
StatusL istenerConfi gHelper . addOnConsoleL istenerInstance(context, new On
Statuslistener
//根據(jù)是否為 debug 模式創(chuàng)建不同的 L ogbackConfigurator
LogbackConfigurator configurator = debug ? new Debugl ogbackConfigurator(c
: new LogbackConfigurator(context);i 配置白志級別將( LEVEL PATTERNE 默認 5setnmironomnti
context . putProperty(LoggingSystemProperties.L0G_ LEVEL_ PATTERN,
environment . resolvePlaceholders("${logging. pattern.le
vel:${LOG_ LEVEL_
PATTERN:%5p}}"));
//配置日志中時間格式
(LOG DATEFORMAT_ PATTERN), 默認為 y>y -M-dd HH: m:ss.sssS
oggingSystemProperties.
LOG
DATEFORMAT_
PATTERN,
environment.
resolvePlacehold
0BB1
"${logging . pattern.
dateformat:${LOG_ DATEFORMAT_ PATTERN:yyy-MM-dd HH:
m.SS}"));
//配置文件名稱格式
(ROLLING FILE_ _NAME_ PATTERN),默認為${LOG _FILE}. %d{yyy -MM-dd}. %i.gz}
(logingSystemProperties . RLLING FILE NANE PATTERN, environment
.resolvePlaceholders("${logging. pattern. rolling-file-name:${LOG_ FILE} .
%yy--d}- .%i.g}));
new DefaultLogbackConfiguration(initializationContext, logFile). apply(confi
gurator);
context . setPackagingDataEnabled(true);
}以 上 代 碼 主 要 進 行 了 LoggerContext 重 置 、 日志輸出格 式 、 日志文 件 名 、Logback-Configurator (編程風格配置 logback)等設(shè)置,具體操作的作用可對照上述代碼中的注解進行了解。
回到最初 L oggingApplicationListener 的 initializeSystem 方法,值得注意的是,在通常情況下,該方法中往往不是直接采用 LoggingSystem 的抽象類 AbstractL oggingSystem 中的initialize 方法實現(xiàn),而是通過不同日志框架重新實現(xiàn),在恰當?shù)臅r機會調(diào)用 AbstractL oggingSystem 的 initialize 方法。這樣做的好處是可以根據(jù)不同的日志框架進行定制化的擴展。比如LogbackL oggingSystem 中 initialize 方法的實現(xiàn)如下。
@Override
public void initialize(LoggingInitializationContext initializationContex
String configLocation, LogFile logFile) {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {return;
super. initialize(initializationContext, configLocation, logFile);
loggerContext. getTurboFilterList() . remove(FILTER);
markAsInitialized(loggerContext);
if (StringUtils. hasText(System. getProperty (CONFIGURATION_ FILE_ PROPERTY)))
{
getLogger(LogbackL oggingSystem. class . getName()) .warn(
"Ignoring '” + CONFIGURATION_ FILE_ PROPERTY +”' system property.'
+ "Please use ' logging. config' instead.");
}
}我們可以看到,在 LogbackL oggingSystem 的實現(xiàn)類中不僅在調(diào)用父類的 initialize 方法之前進行了是否已經(jīng)初始化的判斷,還在調(diào)用父類 initialize 方法之后,實現(xiàn)了它自己的一些業(yè)務(wù)邏輯,比如移除 LoggerContext 的 TurboFilterList 中添加的 TurboFilter、標記初始化狀態(tài)。
在完成了以上步驟之后,日志系統(tǒng)已經(jīng)正式啟動,可以進行正常的日志輸至此,針 對
LoggingApplicationListener 中 ApplicationEnvironmentPreparedEvent 事件的處理已經(jīng)講解完畢。

小結(jié)
本章詳細介紹了 Spring Boot 啟動過程中日志事件的觸發(fā),以及事件發(fā)布之后,日志系統(tǒng)所對應(yīng)的處理。在
LoggingApplicationListener 的 onApplicationEvent 方法中還有其他事件的處理,比如:
ApplicationPreparedEvent、ContextClosedEvent 、ApplicationFailedEvent 等,但相對于上述過程,這些事件的日志處理比較簡單,讀者可自行閱讀。當然,如果對日志系統(tǒng)感興趣,可針對具體的技術(shù)框架進行更加深入地學習。
本文給大家講解的內(nèi)容是Spring Boot日志源碼解析:LoggingApplicationListener的執(zhí)行
下篇文章給大家講解的是創(chuàng)建SpringBoot自動配置項目;
覺得文章不錯的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學習更多的話可以到微信公眾號里找我,我等你哦。
