您當前的位置:首頁 > 攝影

剖析 Spring Boot 日誌框架

作者:由 神馬翔 發表于 攝影時間:2021-03-14

對於程式而言,日誌是其中非常重要的一個部分,可以說,沒有日誌的程式是不完整的。市面上日誌解決方案很多,那 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 這個類。

剖析 Spring Boot 日誌框架

按照我們一貫的想法,這個時候應該使用反射將這個類反射出來。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 的支援,以及對於多環境配置的支援。

如果您覺得有所收穫,就請點個贊吧!

標簽: 日誌  logback  SLF4J  Spring  Boot