jdk-logging?log4j?logback日志系統(tǒng)實(shí)現(xiàn)機(jī)制原理介紹
系列文章已完成,目錄如下:
commons-logging與jdk-logging、log4j1、log4j2、logback的集成原理
slf4j與jul、log4j1、log4j2、logback的集成原理
slf4j、jcl、jul、log4j1、log4j2、logback大總結(jié)
1 需要解決的疑惑
目前的日志框架有jdk自帶的logging,log4j1、log4j2、logback
目前用于實(shí)現(xiàn)日志統(tǒng)一的框架apache的commons-logging、slf4j
為了理清它們的關(guān)系,與繁雜的各種集成jar包,如下:
log4j、log4j-api、log4j-core
log4j-1.2-api、log4j-jcl、log4j-slf4j-impl、log4j-jul
logback-core、logback-classic、logback-access
commons-logging
slf4j-api、slf4j-log4j12、slf4j-simple、jcl-over-slf4j、slf4j-jdk14、log4j-over-slf4j、slf4j-jcl
分成3篇文章來闡述
- jdk自帶的logging、log4j1、log4j2、logback的使用與原理簡述
- slf4j、apache的commons-logging與上述日志框架的集成原理
- slf4j目前與commons-logging混用的境況
2 jdk自帶的logging
2.1 使用案例
private static final Logger logger=Logger.getLogger(JdkLoggingTest.class.getName()); public static void main(String[] args){ logger.info("jdk logging info: a msg"); }
其中的Logger是:java.util.logging.Logger
2.2 簡單過程分析:
不想看源碼的請略過
- 創(chuàng)建一個(gè)LogManager
默認(rèn)是java.util.logging.LogManager,但是也可以自定義,修改系統(tǒng)屬性"java.util.logging.manager"即可,源碼如下(manager就是LogManager):
try { cname = System.getProperty("java.util.logging.manager"); if (cname != null) { try { Class clz = ClassLoader.getSystemClassLoader().loadClass(cname); manager = (LogManager) clz.newInstance(); } catch (ClassNotFoundException ex) { Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname); manager = (LogManager) clz.newInstance(); } } } catch (Exception ex) { System.err.println("Could not load Logmanager \"" + cname + "\""); ex.printStackTrace(); } if (manager == null) { manager = new LogManager(); }
- 加載配置文件
默認(rèn)是jre目錄下的lib/logging.properties文件,也可以自定義修改系統(tǒng)屬性"java.util.logging.config.file",源碼如下:
String fname = System.getProperty("java.util.logging.config.file"); if (fname == null) { fname = System.getProperty("java.home"); if (fname == null) { throw new Error("Can't find java.home ??"); } File f = new File(fname, "lib"); f = new File(f, "logging.properties"); fname = f.getCanonicalPath(); } InputStream in = new FileInputStream(fname); BufferedInputStream bin = new BufferedInputStream(in); try { readConfiguration(bin); }
- 創(chuàng)建Logger,并緩存起來,放置到一個(gè)Hashtable中,并把LogManager設(shè)置進(jìn)新創(chuàng)建的logger中
以tomcat為例,它就自定義了上述配置:
在tomcat的啟動(dòng)文件catalina.bat中,有如下設(shè)置:
- 修改屬性"java.util.logging.manager",自定義LogManager,使用自己的ClassLoaderLogManager
if not "%LOGGING_MANAGER%" == "" goto noJuliManager set LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager :noJuliManager set JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%
- 修改屬性"java.util.logging.config.file",自定義配置文件,使用自己的%CATALINA_BASE%\conf\logging.properties文件
if not "%LOGGING_CONFIG%" == "" goto noJuliConfig set LOGGING_CONFIG=-Dnop if not exist "%CATALINA_BASE%\conf\logging.properties" goto noJuliConfig set LOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties" :noJuliConfig set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%
所以如果想研究tomcat的日志,可以從上面入手。
jdk自帶的logging不再詳細(xì)介紹,有興趣的參見這篇文章JDK Logging深入分析
3 log4j1
3.1 使用案例
3.1.1 需要的jar包
log4j
maven依賴如下:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
3.1.2 使用方式
第一步:編寫log4j.properties配置文件,放到類路徑下
log4j.rootLogger = debug, console log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %m%n
配置文件的詳細(xì)內(nèi)容不是本博客關(guān)注的重點(diǎn),不再說明,自行搜索
第二步:代碼中如下使用
public class Log4jTest { private static final Logger logger=Logger.getLogger(Log4jTest.class); public static void main(String[] args){ if(logger.isTraceEnabled()){ logger.debug("log4j trace message"); } if(logger.isDebugEnabled()){ logger.debug("log4j debug message"); } if(logger.isInfoEnabled()){ logger.debug("log4j info message"); } } }
補(bǔ)充:
1 上述方式默認(rèn)到類路徑下加載log4j.properties配置文件,如果log4j.properties配置文件不在類路徑下,則可以選擇如下方式之一來加載配置文件
使用classLoader來加載資源
PropertyConfigurator.configure(Log4jTest.class.getClassLoader().getResource("properties/log4j.properties"));
使用log4j自帶的Loader來加載資源
PropertyConfigurator.configure(Loader.getResource("properties/log4j.properties"));
3.2 獲取Logger的原理
不想看源碼的請略過
本博客的重點(diǎn)不在于講解log4j的架構(gòu)。只是簡單的說明獲取一個(gè)Logger的過程。分三種情況來說明:
- 第一種情況:沒有指定配置文件路徑
1 第一步: 引發(fā)LogManager的類初始化
Logger.getLogger(Log4jTest.class)的源碼如下:
static public Logger getLogger(Class clazz) { return LogManager.getLogger(clazz.getName()); }
2 第二步:初始化一個(gè)logger倉庫Hierarchy
Hierarchy的源碼如下:
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport { private LoggerFactory defaultFactory; Hashtable ht; Logger root; //其他略 }
LogManager在類初始化的時(shí)候如下方式來實(shí)例化Hierarchy:
static { Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); //略 }
new RootLogger作為root logger,默認(rèn)是debug級別
最后把Hierarchy綁定到LogManager上,可以在任何地方來獲取這個(gè)logger倉庫Hierarchy
- LoggerFactory defaultFactory: 就是創(chuàng)建Logger的工廠
- Hashtable ht:用來存放上述工廠創(chuàng)建的Logger
- Logger root:作為根Logger
3 第三步:在LogManager的類初始化的過程中默認(rèn)尋找類路徑下的配置文件
通過org.apache.log4j.helpers.Loader類來加載類路徑下的配置文件:
Loader.getResource("log4j.xml"); Loader.getResource("log4j.properties")
優(yōu)先選擇xml配置文件
4 第四步:解析上述配置文件
- 如果是xml文件則org.apache.log4j.xml.DOMConfigurator類來解析
- 如果是properties文件,則使用org.apache.log4j.PropertyConfigurator來解析
不再詳細(xì)說明解析過程,看下解析后的結(jié)果:
- 設(shè)置RootLogger的級別
- 對RootLogger添加一系列我們配置的appender(我們通過logger來輸出日志,通過logger中的appender指明了日志的輸出目的地)
5 第五步:當(dāng)一切都準(zhǔn)備妥當(dāng)后,就該獲取Logger了
使用logger倉庫Hierarchy中內(nèi)置的LoggerFactory工廠來創(chuàng)建Logger了,并緩存起來,同時(shí)將logger倉庫Hierarchy設(shè)置進(jìn)新創(chuàng)建的Logger中
- 第二種情況,手動(dòng)來加載不在類路徑下的配置文件
PropertyConfigurator.configure 執(zhí)行時(shí)會(huì)去進(jìn)行上述的配置文件解析,源碼如下:
public static void configure(java.net.URL configURL) { new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository()); }
仍然先會(huì)引發(fā)LogManager的類加載,創(chuàng)建出logger倉庫Hierarchy,同時(shí)嘗試加載類路徑下的配置文件,此時(shí)沒有則不進(jìn)行解析,此時(shí)logger倉庫Hierarchy中的RootLogger默認(rèn)采用debug級別,沒有appender而已。
然后解析配置文件,對上述logger倉庫Hierarchy的RootLogger進(jìn)行級別的設(shè)置,添加appender
此時(shí)再去調(diào)用Logger.getLogger,不會(huì)導(dǎo)致LogManager的類初始化(因?yàn)橐呀?jīng)加載過了)
- 第三種情況,配置文件在類路徑下,而我們又手動(dòng)使用PropertyConfigurator去加載
也就會(huì)造成2次加載解析配置文件,僅僅會(huì)造成覆蓋而已(對于RootLogger進(jìn)行從新設(shè)置級別,刪除原有的appender,重新加載新的appender),所以多次加載解析配置文件以最后一次為準(zhǔn)。
3.3 主要對象總結(jié)
LogManager
: 它的類加載會(huì)創(chuàng)建logger倉庫Hierarchy,并嘗試尋找類路徑下的配置文件,如果有則解析
Hierarchy
: 包含三個(gè)重要屬性:
- LoggerFactory logger的創(chuàng)建工廠
- Hashtable 用于存放上述工廠創(chuàng)建的logger
- Logger root logger,用于承載解析文件的結(jié)果,設(shè)置級別,同時(shí)存放appender
PropertyConfigurator
: 用于解析log4j.properties文件
Logger
: 我們用來輸出日志的對象
4 log4j2
4.1 背景介紹
log4j2與log4j1發(fā)生了很大的變化,不兼容。log4j1僅僅作為一個(gè)實(shí)際的日志框架,slf4j、commons-logging作為門面,統(tǒng)一各種日志框架的混亂格局,現(xiàn)在log4j2也想跳出來充當(dāng)門面了,也想統(tǒng)一大家了。哎,日志格局越來越混亂了。
log4j2分成2個(gè)部分:
- log4j-api: 作為日志接口層,用于統(tǒng)一底層日志系統(tǒng)
- log4j-core : 作為上述日志接口的實(shí)現(xiàn),是一個(gè)實(shí)際的日志框架
4.2 log4j2的使用案例
4.2.1 需要的jar包
log4j-api
log4j-core
對應(yīng)的maven依賴是:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.2</version> </dependency>
4.2.2 使用方式
第一步:編寫log4j2.xml配置文件(目前l(fā)og4j2只支持xml json yuml,不再支持properties文件)
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
第二步: 使用方式
private static final Logger logger=LogManager.getLogger(Log4j2Test.class); public static void main(String[] args){ if(logger.isTraceEnabled()){ logger.debug("log4j trace message"); } if(logger.isDebugEnabled()){ logger.debug("log4j debug message"); } if(logger.isInfoEnabled()){ logger.debug("log4j info message"); } }
和log4j1是不同的。此時(shí)Logger是log4j-api中定義的接口,而log4j1中的Logger則是類
4.3 使用過程簡單分析
不想看源碼的請略過
獲取底層使用的LoggerContextFactory:
同樣LogManager的類加載會(huì)去尋找log4j-api定義的LoggerContextFactory接口的底層實(shí)現(xiàn),獲取方式有三種:
第一種: 嘗試從jar中尋找log4j2.component.properties文件,如果配置了log4j2.loggerContextFactory則使用該LoggerContextFactory
第二種:如果沒找到,嘗試從jar包中尋找META-INF/log4j-provider.properties文件,如log4j-core-2.2中就有該文件,如下圖所示:
如果找到多個(gè),取優(yōu)先級最高的(該文件中指定了LoggerContextFactory,同時(shí)指定了優(yōu)先級FactoryPriority),如log4j-core-2.2中l(wèi)og4j-provider.properties的文件內(nèi)容如下:
LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory Log4jAPIVersion = 2.1.0 FactoryPriority= 10
第三種情況:上述方式還沒找到,就使用默認(rèn)的SimpleLoggerContextFactory
使用LoggerContextFactory獲取LoggerContext
根據(jù)LoggerContext獲取Logger
以log4j-core為例:
- 會(huì)首先判斷LoggerContext是否被初始化過了,沒有則進(jìn)行初始化
- 獲取ConfigurationFactory,從配置中獲取和插件中獲?。╨og4j-core核心包中有三個(gè)YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory)
- 以上文的案例中,會(huì)使用XmlConfigurationFactory來加載log4j2.xml配置文件
- LoggerContext初始化后,就可以獲取或者創(chuàng)建Logger了
4.4 主要對象總結(jié)
LogManager
: 它的類加載會(huì)去尋找LoggerContextFactory接口的底層實(shí)現(xiàn),會(huì)從jar包中的配置文件中尋找,如上面所述
LoggerContextFactory
: 用于創(chuàng)建LoggerContext,不同的日志實(shí)現(xiàn)系統(tǒng)會(huì)有不同的實(shí)現(xiàn),如log4j-core中的實(shí)現(xiàn)為Log4jContextFactory
PropertyConfigurator
: 用于解析log4j.properties文件
LoggerContext
: 它包含了配置信息,并能創(chuàng)建log4j-api定義的Logger接口實(shí)例,并緩存這些實(shí)例
ConfigurationFactory
:上述LoggerContext解析配置文件,需要用到ConfigurationFactory,目前有三個(gè)YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory,分別解析yuml json xml形式的配置文件
5 logback
5.1 使用案例
5.1.1 需要的jar包
logback-core
logback-classic
slf4j-api
對應(yīng)的maven依賴為:
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency>
5.1.2 使用方式
private static final Logger logger=LoggerFactory.getLogger(LogbackTest.class); public static void main(String[] args){ if(logger.isDebugEnabled()){ logger.debug("slf4j-logback debug message"); } if(logger.isInfoEnabled()){ logger.debug("slf4j-logback info message"); } if(logger.isTraceEnabled()){ logger.debug("slf4j-logback trace message"); } LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); StatusPrinter.print(lc); }
補(bǔ)充:
官方使用方式,其實(shí)就和slf4j集成了起來
上述的Logger、LoggerFactory都是slf4j自己的接口與類
沒有配置文件的情況下,使用的是默認(rèn)配置。搜尋配置文件的過程如下:
Logback tries to find a file called logback.groovy in the classpath.
If no such file is found, logback tries to find a file called logback-test.xml in the classpath.
If no such file is found, it checks for the file logback.xml in the classpath..
If no such file is found, and the executing JVM has the ServiceLoader (JDK 6 and above) the ServiceLoader will be used to resolve an implementation of com.qos.logback.classic.spi.Configurator. The first implementation found will be used. See ServiceLoader documentation for more details.
If none of the above succeeds, logback configures itself automatically using the BasicConfigurator which will cause logging output to be directed to the console.
The fourth and last step is meant to provide a default (but very basic) logging functionality in the absence of a configuration file.
也可以在類路徑下加上一個(gè)類似如下的logback.xml的配置文件,如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> </configuration>
logback則會(huì)去解析對應(yīng)的配置文件。
5.3 使用過程簡單分析
不想看源碼的請略過
slf4j與底層的日志系統(tǒng)進(jìn)行綁定
在jar包中尋找org/slf4j/impl/StaticLoggerBinder.class 這個(gè)類,如在logback-classic中就含有這個(gè)類,如下圖所示
如果找到多個(gè)StaticLoggerBinder,則表明目前底層有多個(gè)實(shí)際的日志框架,slf4j會(huì)隨機(jī)選擇一個(gè)
使用上述找到的StaticLoggerBinder創(chuàng)建一個(gè)實(shí)例,并返回一個(gè)ILoggerFactory實(shí)例:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
以logback-classic中的StaticLoggerBinder為例,在StaticLoggerBinder.getSingleton()過程中:會(huì)去加載解析配置文件 源碼如下:
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) { ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this); //尋找logback.configurationFile的系統(tǒng)屬性 URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus); if (url != null) { return url; } //尋找logback.groovy url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus); if (url != null) { return url; } //尋找logback-test.xml url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus); if (url != null) { return url; } //尋找logback.xml return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus); }
目前路徑都是定死的,只有l(wèi)ogback.configurationFile的系統(tǒng)屬性是可以更改的,所以如果我們想更改配置文件的位置(不想放在類路徑下),則需要設(shè)置這個(gè)系統(tǒng)屬性:
System.setProperty("logback.configurationFile", "/path/to/config.xml");
解析完配置文件后,返回的ILoggerFactory實(shí)例的類型是LoggerContext(它包含了配置信息)
根據(jù)返回的ILoggerFactory實(shí)例,來獲取Logger
就是根據(jù)上述的LoggerContext來創(chuàng)建一個(gè)Logger,每個(gè)logger與LoggerContext建立了關(guān)系,并放到LoggerContext的緩存中,就是LoggerContext的如下屬性:
private Map<String, Logger> loggerCache;
其實(shí)上述過程就是slf4j與其他日志系統(tǒng)的綁定過程。不同的日志系統(tǒng)與slf4j集成,都會(huì)有一個(gè)StaticLoggerBinder類,并會(huì)擁有一個(gè)ILoggerFactory的實(shí)現(xiàn)。
6結(jié)尾
這一篇文章簡單介紹了幾種日志的簡單用法,下一篇文章就來介紹他們與commons-logging和slf4j怎么集成起來,就是要弄清楚各種集成jar包做了哪些事情,更多關(guān)于jdk-logging log4j logback日志系統(tǒng)機(jī)制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解IDEA社區(qū)版(Community)和付費(fèi)版(UItimate)的區(qū)別
這篇文章主要介紹了詳解IDEA社區(qū)版(Community)和付費(fèi)版(UItimate)的區(qū)別,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11Java 并發(fā)編程:volatile的使用及其原理解析
下面小編就為大家?guī)硪黄狫ava 并發(fā)編程:volatile的使用及其原理解析。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-05-05springboot實(shí)現(xiàn)mock平臺(tái)的示例代碼
本文主要介紹了springboot實(shí)現(xiàn)mock平臺(tái)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06解決springcloud 配置gateway 出現(xiàn)錯(cuò)誤的問題
今天給大家分享springcloud 配置gateway 出現(xiàn)錯(cuò)誤的問題,其實(shí)解決方法很簡單,只需要降低springcloud版本,改成Hoxton.SR5就好了,再次改成Hoxton.SR12,也不報(bào)錯(cuò)了,下面給大家展示下,感興趣的朋友一起看看吧2021-11-11MyBatis插入Insert、InsertSelective的區(qū)別及使用心得
這篇文章主要介紹了MyBatis插入Insert、InsertSelective的區(qū)別及使用心得,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12java?list和map切割分段的實(shí)現(xiàn)及多線程應(yīng)用案例
這篇文章主要為大家介紹了java?list和map切割分段的實(shí)現(xiàn)及多線程應(yīng)用案例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12