剖析 Spring Boot 日誌框架
對於程式而言,日誌是其中非常重要的一個部分,可以說,沒有日誌的程式是不完整的。市面上日誌解決方案很多,那 Spring Boot 提供的怎樣的日誌解決方案呢?我們是否可以借鑑 Spring Boot 的方案呢?本文在分析 Spring Boot 日誌方案的同時,探討以下幾點內容。
Java 日誌體系梳理。
日誌框架如何載入日誌實現的。
Spring Boot 日誌載入流程。
Spring Boot 日誌配置是如何生效的?
Spring Boot 日誌整合
Spring Boot 日誌體系依賴於 spring-boot-starter-logging。如下所示,我們在開發的時候,並不需要主動引用這個 jar 包。因為它預設包含在了 spring-boot-starter 中。
org。springframework。boot
spring-boot-starter-logging
使用起來也很簡單,在成員變數中申明一個 logger,將當前類作為引數插入工廠方法中,就可以使用
http://
logger。info
(“xxx”) 進行日誌輸出了。
private
final
Logger
logger
=
LoggerFactory
。
getLogger
(
DemoInitializer
。
class
);
我們看下 spring-boot-starter-logging 中包含了什麼?
ch。qos。logback
logback-classic
1。2。3
compile
org。apache。logging。log4j
log4j-to-slf4j
2。13。3
compile
org。slf4j
jul-to-slf4j
1。7。30
compile
logback-classic 實現了slf4j。說明
Spring Boot 採用的是 slf4j + logback 的日誌方式
。
log4j-to-slf4j 是一個 log4j 到 slf4j 介面卡,用於將使用了 log4j API 的程式重定向到 slf4j。
jul-to-slf4j 和 log4j-to-slf4j 類似,實現 jul 日誌重定向到 slf4j。
slf4j 全稱是 Simple Logging Facade for Java。是一個 Java 日誌門面框架,通俗的講就是一個日誌的抽象介面,它本身不負責日誌的實現。
logback 是一個遵循 slf4j 規範的日誌實現,我們呼叫 slf4j 介面列印的時候,slf4j 底層呼叫的其實是 logback。
Java 日誌體系
slf4j + logback 是當前 java 日誌體系中比較推薦的方式。我們按照時間順序梳理下java 日誌體系。
log4j 是最早出現的日誌系統,再此之前使用者只能用 java 自帶的 system。out。print() 進行日誌列印。log4j 出現後,作為唯一的日誌系統,很快被大量的使用,成為 java 日誌事實上的標準,直到現在,仍然有大量的專案使用 log4j。
jdk 不甘寂寞,在 1。4 中增加了 JUL(Java util logging) 日誌實現,由於 JUL 內置於 jdk 中,無需引用任何 jar 包,加上 sun 公司的光環,在當時吸引了很多使用者。但是 JUL 的使用存在一些缺陷,現在應該很少有人使用 JUL 了。
鑑於市面上並存 log4j 和 JUL 兩種日誌系統,Apache 提供了 Commons Logging 解決這種混亂。Commons Logging 被稱為 JCL,它可以掛接不同的日誌系統,並透過配置檔案指定掛接的日誌系統。預設情況下,JCL 自動搜尋並使用 Log4j,如果沒有找到Log4j,再使用 JUL。這樣看來,JCL 確定能夠滿足大部分的場景,基本實現了一統江湖,就連 spring 也是依賴了 JCL。
事情還沒完,log4j 的作者覺得 JCL 提供的介面不夠優秀,他自己要做一套更優雅的出來,於是有了 slf4j。slf4j 和 JCL 的定位一致,也確實很更加好用。不僅如此,作者親自為 slf4j 做了一套實現,也就是 logback。效能要高於 log4j。slf4j 還提供了一些橋接的方式將 JCL 轉變過來。Spring Boot 目前採用的就是這種方式。
目前已經不建議使用 log4j 了,因為 log4j 在 2015 年停止維護,作者去維護 log4j2 了。一般框架層使用 slf4j,實現層使用 log4j2 或者是 logback。得益於 log4j2 和 logback 是一個作者,目前主流的日誌使用方式趨近於一致。
slf4j 是如何載入 logback 的?
slf4j 開始呼叫日誌的入口是 getLogger 方法。
public
static
Logger
getLogger
(
String
name
)
{
ILoggerFactory
iLoggerFactory
=
getILoggerFactory
();
return
iLoggerFactory
。
getLogger
(
name
);
}
getILoggerFactory 中有一系列的狀態判斷,只要是判斷當前環境中 ILoggerFactory 的初始化狀態。首次呼叫的時候,肯定還沒有載入,執行 performInitialization 方法進行初始化。performInitialization 中執行 bind 方法。
public
static
ILoggerFactory
getILoggerFactory
()
{
if
(
INITIALIZATION_STATE
==
UNINITIALIZED
)
{
synchronized
(
LoggerFactory
。
class
)
{
if
(
INITIALIZATION_STATE
==
UNINITIALIZED
)
{
INITIALIZATION_STATE
=
ONGOING_INITIALIZATION
;
performInitialization
();
}
}
}
switch
(
INITIALIZATION_STATE
)
{
case
SUCCESSFUL_INITIALIZATION
:
return
StaticLoggerBinder
。
getSingleton
()。
getLoggerFactory
();
。。。
}
}
slf4j 本身不提供日誌的實現,所以在 bind 階段它會去找可能存在的 slf4j 的實現。具體是如何約定的呢?我們需要關注下 findPossibleStaticLoggerBinderPathSet 方法。
private
final
static
void
bind
()
{
try
{
Set
<
URL
>
staticLoggerBinderPathSet
=
null
;
if
(!
isAndroid
())
{
// 找出可能存在的 slf4j 的實現
staticLoggerBinderPathSet
=
findPossibleStaticLoggerBinderPathSet
();
}
。。。
}
。。。
}
findPossibleStaticLoggerBinderPathSet 的核心思路是,
查詢 org/slf4j/impl/StaticLoggerBinder.class 這個類,如果有多個,那就返回多個的路徑
。
// 約定的包路徑
private
static
String
STATIC_LOGGER_BINDER_PATH
=
“org/slf4j/impl/StaticLoggerBinder。class”
;
static
Set
<
URL
>
findPossibleStaticLoggerBinderPathSet
()
{
Set
<
URL
>
staticLoggerBinderPathSet
=
new
LinkedHashSet
<
URL
>();
try
{
// 獲得 classLoader
ClassLoader
loggerFactoryClassLoader
=
LoggerFactory
。
class
。
getClassLoader
();
Enumeration
<
URL
>
paths
;
if
(
loggerFactoryClassLoader
==
null
)
{
paths
=
ClassLoader
。
getSystemResources
(
STATIC_LOGGER_BINDER_PATH
);
}
else
{
// 呼叫 classLoader 的 getResources 方法
paths
=
loggerFactoryClassLoader
。
getResources
(
STATIC_LOGGER_BINDER_PATH
);
}
while
(
paths
。
hasMoreElements
())
{
URL
path
=
paths
。
nextElement
();
staticLoggerBinderPathSet
。
add
(
path
);
}
}
catch
(
IOException
ioe
)
{
Util
。
report
(
“Error getting resources from path”
,
ioe
);
}
return
staticLoggerBinderPathSet
;
}
我們在 logback 的包路徑下找到 org/slf4j/impl/StaticLoggerBinder。class 這個類。
按照我們一貫的想法,這個時候應該使用反射將這個類反射出來。slf4j 不是這麼做的,它直接將 org。slf4j。impl。StaticLoggerBinder 引入,然後呼叫其相應的方法建立了 ILoggerFactory。
package
org。slf4j
;
import
org。slf4j。impl。StaticLoggerBinder
;
public
static
ILoggerFactory
getILoggerFactory
()
{
。。。
switch
(
INITIALIZATION_STATE
)
{
case
SUCCESSFUL_INITIALIZATION
:
return
StaticLoggerBinder
。
getSingleton
()。
getLoggerFactory
();
。。。
}
}
slf4j 採用約定優先的思想,約定好實現類的路徑,直接在程式碼中引入實現類,生成日誌工廠例項
。這種方式簡單且高效。
Spring Boot 載入日誌流程
我們上面講了 Spring Boot 專案推薦使用的日誌方式是 slf4j + logback。那 Spring Boot 專案本身使用的日誌框架是什麼呢?
Spring Boot 和 Spring 保持一致,使用 JCL 作為日誌框架,這樣可以在不引入任何額外的 jar 包 的情況下列印日誌。
我們啟動 Spring Boot 程式,會 new 一個 SpringApplication 例項。該例項有一個成員變數,就是 logger。這個 logger 的型別是 org。apache。commons。logging。Log。
import
org。apache。commons。logging。Log
;
import
org。apache。commons。logging。LogFactory
;
private
static
final
Log
logger
=
LogFactory
。
getLog
(
SpringApplication
。
class
);
那 Spring Boot 列印的日誌為什麼會和我們使用 slf4j 時候一致呢?我們具體看下後續的流程。getLog 方法中使用 LogAdapter 獲得真正得 Log 實現。
public
static
Log
getLog
(
String
name
)
{
return
LogAdapter
。
createLog
(
name
);
}
LogAdapter 會根據當前專案是否存在執行的類來判斷使用的是哪種日誌系統
。LogAdapter 中定義了這麼幾種情況:
有 log4j2 的依賴,且沒有橋接到 slf4j,使用 Log4j 2。x。
有 log4j2 的依賴,且橋接到 slf4j,使用 slf4j。
沒有 log4j2 的依賴,但是有 slf4j(忽略 slf4j 的不同用法),使用 slf4j。
不滿足以上情況,使用 JUL。
final
class
LogAdapter
{
// log4j2 提供
private
static
final
String
LOG4J_SPI
=
“org。apache。logging。log4j。spi。ExtendedLogger”
;
// log4j-to-slf4j 提供
private
static
final
String
LOG4J_SLF4J_PROVIDER
=
“org。apache。logging。slf4j。SLF4JProvider”
;
// slf4j-api 提供
private
static
final
String
SLF4J_SPI
=
“org。slf4j。spi。LocationAwareLogger”
;
// slf4j-api 提供
private
static
final
String
SLF4J_API
=
“org。slf4j。Logger”
;
private
static
final
LogApi
logApi
;
static
{
if
(
isPresent
(
LOG4J_SPI
))
{
if
(
isPresent
(
LOG4J_SLF4J_PROVIDER
)
&&
isPresent
(
SLF4J_SPI
))
{
// log4j-to-slf4j bridge -> we‘ll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support。
logApi
=
LogApi
。
SLF4J_LAL
;
}
else
{
// Use Log4j 2。x directly, including location awareness support
logApi
=
LogApi
。
LOG4J
;
}
}
else
if
(
isPresent
(
SLF4J_SPI
))
{
// Full SLF4J SPI including location awareness support
logApi
=
LogApi
。
SLF4J_LAL
;
}
else
if
(
isPresent
(
SLF4J_API
))
{
// Minimal SLF4J API without location awareness support
logApi
=
LogApi
。
SLF4J
;
}
else
{
// java。util。logging as default
logApi
=
LogApi
。
JUL
;
}
}
private
static
boolean
isPresent
(
String
className
)
{
try
{
Class
。
forName
(
className
,
false
,
LogAdapter
。
class
。
getClassLoader
());
return
true
;
}
catch
(
ClassNotFoundException
ex
)
{
return
false
;
}
}
。。。
}
為什麼 jcl 中會有 slf4j 的內容,進一步我們發現,Spring 5 中對於日誌系統進行了更新。
spring4 所依賴的是 jcl,而 spring5 則依賴 spring-jcl
。spring-jcl 是 spring 內部對於 jcl 的升級,在維持原先 jcl 介面基礎上,增加了對於 log4j2 和 slf4j 的支援。這樣 spring 不需要改程式碼,只通過修改依賴,就將日誌系統切換到了 log4j2 或者是 slf4j 上。
返回之前的 createLog 方法看,會根據 LogAdapter 靜態方法中判斷出的當前依賴 jar 包的情況,使用對應的介面卡來建立真正的日誌系統。
public
static
Log
createLog
(
String
name
)
{
switch
(
logApi
)
{
case
LOG4J
:
return
Log4jAdapter
。
createLog
(
name
);
case
SLF4J_LAL
:
return
Slf4jAdapter
。
createLocationAwareLog
(
name
);
case
SLF4J
:
return
Slf4jAdapter
。
createLog
(
name
);
default
:
return
JavaUtilAdapter
。
createLog
(
name
);
}
}
由於 spring-boot-starter-logging 中包含了 slf4j 的依賴,所以這裡就呼叫了 Slf4jAdapter。後續就是委託 slf4j 的 LoggerFactory 進行日誌系統建立。完美的完成了從 jcl 到 slf4j 的轉換。
private
static
class
Slf4jAdapter
{
public
static
Log
createLocationAwareLog
(
String
name
)
{
Logger
logger
=
LoggerFactory
。
getLogger
(
name
);
return
(
logger
instanceof
LocationAwareLogger
?
new
Slf4jLocationAwareLog
((
LocationAwareLogger
)
logger
)
:
new
Slf4jLog
<>(
logger
));
}
}
Spring Boot 中日誌配置是如何生效的
本小節較為瑣碎,如果對內部原理不感興趣,小節末尾有結論,可以直接看。
這是一份 logback 的配置,放在 application。properties 的同級目錄下。配置中每一項的含義我們不介紹,我們瞭解下配置檔案的載入時機和策略。
<?xml version=“1。0” encoding=“UTF-8”?>
debug= “false” > <!—— 檔案路徑 ——> name= “logPath” value= “。/logs” /> <!—— 日誌級別 ——> name= “logLevel” value= “INFO” /> <!—— 控制檯輸出 ——> name= “STDOUT” class= “ch。qos。logback。core。ConsoleAppender” > class= “ch。qos。logback。classic。encoder。PatternLayoutEncoder” > <!——輸出格式——> %d{yyyy-MM-dd HH:mm:ss。SSS} [%thread] %-5level %logger{50} - %msg%n <!—— 日誌檔案 ——> name= “FILE” class= “ch。qos。logback。core。rolling。RollingFileAppender” > class= “ch。qos。logback。core。rolling。TimeBasedRollingPolicy” > <!——檔名——> ${logPath}/log。%d{yyyy-MM-dd}。log <!——保留天數——> 30 class= “ch。qos。logback。classic。encoder。PatternLayoutEncoder” > <!——輸出格式——> %d{yyyy-MM-dd HH:mm:ss。SSS} [%thread] %-5level %logger{50} - %msg%n <!——日誌檔案切割臨界值——> class= “ch。qos。logback。core。rolling。SizeBasedTriggeringPolicy” > 10MB level= “${logLevel}” > ref= “STDOUT” /> ref= “FILE” />
我們之前談到,slf4j 在使用 getILoggerFactory 獲得日誌工廠類的時候,會按照約定透過 ClassLoader 載入指定位置的實現類。如果有且僅有一個話,說明存在實現類。接下來就是獲得實現類(以 logback 為例)的配置了。
logback 中負責獲得並解析配置的是 StaticLoggerBinder。該類有一個靜態程式碼塊,會在靜態程式碼塊中載入指定的配置檔案。
首先是看系統變數中有沒有指定 logback。configurationFile,有的話,載入這個檔案。
查詢檔案的順序依次是 logback-test。xml -> logback。groovy -> logback。configurationFile -> logback。xml。
如果一個配置檔案都沒有,logback 提供了 BasicConfigurator,這是預設配置,會將日誌輸出到控制檯。
final
public
static
String
GROOVY_AUTOCONFIG_FILE
=
“logback。groovy”
;
final
public
static
String
AUTOCONFIG_FILE
=
“logback。xml”
;
final
public
static
String
TEST_AUTOCONFIG_FILE
=
“logback-test。xml”
;
final
public
static
String
CONFIG_FILE_PROPERTY
=
“logback。configurationFile”
;
public
URL
findURLOfDefaultConfigurationFile
(
boolean
updateStatus
)
{
ClassLoader
myClassLoader
=
Loader
。
getClassLoaderOfObject
(
this
);
URL
url
=
findConfigFileURLFromSystemProperties
(
myClassLoader
,
updateStatus
);
if
(
url
!=
null
)
{
return
url
;
}
url
=
getResource
(
TEST_AUTOCONFIG_FILE
,
myClassLoader
,
updateStatus
);
if
(
url
!=
null
)
{
return
url
;
}
url
=
getResource
(
GROOVY_AUTOCONFIG_FILE
,
myClassLoader
,
updateStatus
);
if
(
url
!=
null
)
{
return
url
;
}
return
getResource
(
AUTOCONFIG_FILE
,
myClassLoader
,
updateStatus
);
}
以上是 logback 載入日誌配置的方式,但是在 Spring Boot 中,我們通常會在 application。properties 配置一些以 logging 開頭的配置,比如 logging。file。path,logging。file。name 等。這些配置項需要依賴 Spring Boot 的能力才能被讀取到。這個時間點是晚於 logback 載入日誌配置的。所以如果我們配置了 application。properties,但是使用的配置檔案是 logback。xml,那 application。properties 中的配置是不生效的。
Spring Boot 推薦的配置檔案的方式是使用 logback-spring.xml + application.properties 的方式
那麼 Spring Boot 是如何將日誌本身的配置和 Spring Boot 提供的配置相結合的呢?
我們在 Spring Boot 是如何監聽啟動事件的 一文中談到過,Spring Boot 在啟動的不同階段會發出不同的事件。我們可以透過實現 ApplicationListener 來監聽這些事件。
在 spring-boot 的 jar 包下的 META-INF 中找到 spring。factories,ApplicationListener 的實現類中有一個是 LoggingApplicationListener。這個監聽器是實現 Spring Boot 日誌自定義配置的關鍵。
#
Application
Listeners
org
。
springframework
。
context
。
ApplicationListener
=
org
。
springframework
。
boot
。
context
。
logging
。
LoggingApplicationListener
,
。。。
從 onApplicationEvent 方法中可以看出,LoggingApplicationListener 監聽了多個事件,包括 ApplicationStartingEvent,ApplicationEnvironmentPreparedEvent,ApplicationPreparedEvent,ContextClosedEvent 以及 ApplicationFailedEvent。這些事件中最需要關注的是 ApplicationEnvironmentPreparedEvent。因為該事件表示應用的配置資訊已經解析完畢了。
@Override
public
void
onApplicationEvent
(
ApplicationEvent
event
)
{
// 獲得當前的日誌系統是 Logback,log4j2 或是 java
// 本質是判斷是否存在約定的類
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
();
}
}
onApplicationEnvironmentPreparedEvent 方法會呼叫 initialize 來重新初始化日誌配置。
private
void
onApplicationEnvironmentPreparedEvent
(
ApplicationEnvironmentPreparedEvent
event
)
{
if
(
this
。
loggingSystem
==
null
)
{
this
。
loggingSystem
=
LoggingSystem
。
get
(
event
。
getSpringApplication
()。
getClassLoader
());
}
initialize
(
event
。
getEnvironment
(),
event
。
getSpringApplication
()。
getClassLoader
());
}
initialize 方法的呼叫棧比較長,就不列出程式碼了,簡單的講下每個方法做了些什麼。
protected
void
initialize
(
ConfigurableEnvironment
environment
,
ClassLoader
classLoader
)
{
// 從所有的配置中讀取日誌相關的配置
getLoggingSystemProperties
(
environment
)。
apply
();
// 透過 logging。file。name 以及 logging。file。path 生成 logFile 物件
// 該物件單獨處理的原因是這兩個物件在純配置模式下決定是否生成日誌檔案
this
。
logFile
=
LogFile
。
get
(
environment
);
// 如果 logFile 非空,將上述兩個配置也放到 LoggingSystemProperties 中
if
(
this
。
logFile
!=
null
)
{
this
。
logFile
。
applyToSystemProperties
();
}
this
。
loggerGroups
=
new
LoggerGroups
(
DEFAULT_GROUP_LOGGERS
);
initializeEarlyLoggingLevel
(
environment
);
// 讀取日誌系統配置
initializeSystem
(
environment
,
this
。
loggingSystem
,
this
。
logFile
);
initializeFinalLoggingLevels
(
environment
,
this
。
loggingSystem
);
registerShutdownHookIfNecessary
(
environment
,
this
。
loggingSystem
);
}
Spring Boot 定義了一個 LoggingSystemProperties,這個類是專門用於儲存和解析有關於日誌的配置的,它儲存了一些日誌系統共用的配置,比如 logging。pattern。file,logging。pattern。level 等等。它的子類和日誌系統有關,比如 logback 就是 LogbackLoggingSystemProperties,它儲存了該日誌系統相關的配置,比如 logging。logback。rollingpolicy。max-file-size 等等。所以在應用的配置資訊都準備好之後,會根據這些配置生成 LoggingSystemProperties 物件。
有了這個配置物件後,Spring Boot 會重新進行日誌配置的解析。這一次解析配置的主體由 logback 變為了 Spring Boot。解析檔案的順序和 logback 解析檔案的順序是一致的。
@Override
protected
String
[]
getStandardConfigLocations
()
{
return
new
String
[]
{
“logback-test。groovy”
,
“logback-test。xml”
,
“logback。groovy”
,
“logback。xml”
};
}
如果這幾個檔案都不存在,那麼需要獲得另外的配置檔案,規則是在原先的配置檔名稱後加上 -spring,檔案型別不變。
protected
String
[]
getSpringConfigLocations
()
{
String
[]
locations
=
getStandardConfigLocations
();
for
(
int
i
=
0
;
i
<
locations
。
length
;
i
++)
{
String
extension
=
StringUtils
。
getFilenameExtension
(
locations
[
i
]);
locations
[
i
]
=
locations
[
i
]。
substring
(
0
,
locations
[
i
]。
length
()
-
extension
。
length
()
-
1
)
+
“-spring。”
+
extension
;
}
return
locations
;
}
Spring Boot 解析日誌配置檔案和 logback 解析配置檔案是不一樣的,Spring Boot 允許日誌的配置檔案中存在環境變數(不是所有,是特定的)。因為 Spring Boot 能很方便的解析這些變數。而 logback 不行。這也是 Spring Boot 推薦我們使用 logback-spring。xml 進行配置的原因,畢竟在 logback。xml 是沒辦法讀取 Spirng Boot 配置的變數的。
還有一個很重要的原因就是,Spring Boot 解析日誌檔案的時候,增加了一個解析 springProfile 節點的規則,這個節點為我們多環境配置提供了基礎。有了它,我們可以很方便的為不同的環境定製不同的日誌配置。
@Override
public
void
addInstanceRules
(
RuleStore
rs
)
{
super
。
addInstanceRules
(
rs
);
Environment
environment
=
this
。
initializationContext
。
getEnvironment
();
rs
。
addRule
(
new
ElementSelector
(
“configuration/springProperty”
),
new
SpringPropertyAction
(
environment
));
rs
。
addRule
(
new
ElementSelector
(
“*/springProfile”
),
new
SpringProfileAction
(
environment
));
rs
。
addRule
(
new
ElementSelector
(
“*/springProfile/*”
),
new
NOPAction
());
}
假設我們在 application。properties 進行了如下配置,全量的配置請參考官方文件Common Application properties
logging。file。path=。/logs
logging。file。name=log
logging。pattern。level=ERROR
我們就可以在 logback-spring。xml 中使用 LOG_PATH ,LOG_FILE 和 LOG_LEVEL_PATTERN 這些變數。springProfile 節點用於判斷當前的環境是否滿足條件。在實際專案中,可以根據需求靈活使用這些環境變數。
<?xml version=“1。0” encoding=“UTF-8”?>
debug= “false” > <!—— 控制檯輸出 ——> name= “STDOUT” class= “ch。qos。logback。core。ConsoleAppender” > class= “ch。qos。logback。classic。encoder。PatternLayoutEncoder” > <!——輸出格式——> %d{yyyy-MM-dd HH:mm:ss。SSS} [%thread] %-5level %logger{50} - %msg%n <!—— 日誌檔案 ——> name= “FILE” class= “ch。qos。logback。core。rolling。RollingFileAppender” > class= “ch。qos。logback。core。rolling。TimeBasedRollingPolicy” > <!——檔名——> ${LOG_PATH}/${LOG_FILE}。%d{yyyy-MM-dd}。log <!——保留天數——> 30 class= “ch。qos。logback。classic。encoder。PatternLayoutEncoder” > <!——輸出格式——> %d{yyyy-MM-dd HH:mm:ss。SSS} [%thread] %-5level %logger{50} - %msg%n <!——日誌檔案切割臨界值——> class= “ch。qos。logback。core。rolling。SizeBasedTriggeringPolicy” > 10MB name= “dev” > level= “${LOG_LEVEL_PATTERN}” > ref= “STDOUT” /> ref= “FILE” /> name= “test,prod” > level= “${LOG_LEVEL_PATTERN}” > ref= “FILE” />
還有一種情況是,我們專案沒有任何的配置檔案,所有的配置都寫在 application。properties 中,Sping Boot 也支援這種寫法。但是這樣配置限制較大,配置項只能滿足較為簡單的場景。我們總結下 Spring Boot 對於 logback 配置方面做的增強。
首先 logback 在初始化的時候會自動載入 logback。xml 配置檔案(為了簡化描述,忽略其它的檔名)。此時 Spring 環境未初始化完畢,所有的配置都不會生效,logback。xml 的編寫也需遵循 logback 標準寫法。如果沒有配置檔案,logback 預設有一個配置方案,也就是列印到控制檯。
Spring Boot 內建了一個監聽器,監聽到環境變數初始化完畢的時間後後,會重新配置 logback。此時 application。properties 中的配置解析完畢。
判斷是否存在原生配置檔案,也就是 logback。xml。如果存在,且 application。properties 中沒有配置 logging。file。path 或 logging。file。name。就以 logback。xml 中的配置為準。流程停止。其實就算是配置了 logging。file。path 或 logging。file。name 也不會生效,只是多了一步將這個配置寫入日誌配置類的流程。
判斷是否存在 logback-sping。xml。如果存在,Sping Boot 會解析這個檔案,logback-sping。xml 不僅可以使用 application。properties 中的配置(特定的幾個),還能使用 springProfile 節點做環境判斷。
不存在任何配置檔案,以 application。properties 中的配置引數構造一個預設的配置。構造的規則看官方文件Common Application properties中每個配置的含義就可以了。
總結
Java 日誌體系中,JUL 已經成為過去時,log4j 停止更新。jcl 在 spring 5 後也名存實亡。推薦使用 slf4j 作為日誌門面框架,logback 或者 log4j2 作為日誌系統。新系統如果要加日誌,最簡單的做法是引入 spring-boot-starter-logging 包,一次性引入 slf4j 和 logback。
slf4j 採用約定優先的思想,約定好實現類的路徑,直接在程式碼中引入實現類,生成日誌工廠例項。這就是 slf4j 實現日誌門面的原理。
Spring Boot 和 Spring 的日誌方案是一致的,使用 jcl 作為系統日誌,Spring 5 之後,將 jcl 更新為 spring-jcl,spring-jcl 保留了原先的介面,但是實現確是根據當前專案中存在的類,將日誌系統橋接到 log4j2 或 slf4j,至此,jcl 在 Spring 中名存實亡。
Spring Boot 預設引入 spring-boot-starter-logging 包,採用 slf4j + logback 的方式記錄日誌。由於 logback 本身會讀取配置檔案,且時間優先於 Spring 環境變數的初始化。Spring Boot 使用事件監聽的方式,重新設計了一套配置解析方式,增加了日誌配置對於 application。properties 的支援,以及對於多環境配置的支援。
如果您覺得有所收穫,就請點個贊吧!
上一篇:來我的QQ空間“踩一踩”
下一篇:光強分佈類畢業論文文獻都有哪些?