系列文章已完成,目錄以下:java
#1 須要解決的疑惑apache
目前的日誌框架有jdk自帶的logging,log4j一、log4j二、logbackjson
目前用於實現日誌統一的框架apache的commons-logging、slf4japi
爲了理清它們的關係,與繁雜的各類集成jar包,以下:緩存
分紅3篇文章來闡述tomcat
#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.Loggerapp
##2.2 簡單過程分析:框架
不想看源碼的請略過
建立一個LogManager
默認是java.util.logging.LogManager,可是也能夠自定義,修改系統屬性"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(); }
加載配置文件
默認是jre目錄下的lib/logging.properties文件,也能夠自定義修改系統屬性"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); }
建立Logger,並緩存起來,放置到一個Hashtable中,並把LogManager設置進新建立的logger中
以tomcat爲例,它就自定義了上述配置:
在tomcat的啓動文件catalina.bat中,有以下設置:
修改屬性"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再也不詳細介紹,有興趣的參見這篇文章JDK Logging深刻分析
#3 log4j1
##3.1 使用案例
###3.1.1 須要的jar包
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
配置文件的詳細內容不是本博客關注的重點,再也不說明,自行搜索
第二步:代碼中以下使用
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"); } } }
補充:
1 上述方式默認到類路徑下加載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的原理
不想看源碼的請略過
本博客的重點不在於講解log4j的架構。只是簡單的說明獲取一個Logger的過程。分三種狀況來講明:
第一種狀況:沒有指定配置文件路徑
1 第一步: 引起LogManager的類初始化
Logger.getLogger(Log4jTest.class)的源碼以下:
static public Logger getLogger(Class clazz) { return LogManager.getLogger(clazz.getName()); }
2 第二步:初始化一個logger倉庫Hierarchy
Hierarchy的源碼以下:
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport { private LoggerFactory defaultFactory; Hashtable ht; Logger root; //其餘略 }
LogManager在類初始化的時候以下方式來實例化Hierarchy:
static { Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); //略 }
new RootLogger做爲root logger,默認是debug級別
最後把Hierarchy綁定到LogManager上,能夠在任何地方來獲取這個logger倉庫Hierarchy
3 第三步:在LogManager的類初始化的過程當中默認尋找類路徑下的配置文件
經過org.apache.log4j.helpers.Loader類來加載類路徑下的配置文件:
Loader.getResource("log4j.xml"); Loader.getResource("log4j.properties")
優先選擇xml配置文件
4 第四步:解析上述配置文件
再也不詳細說明解析過程,看下解析後的結果:
5 第五步:當一切都準備穩當後,就該獲取Logger了
使用logger倉庫Hierarchy中內置的LoggerFactory工廠來建立Logger了,並緩存起來,同時將logger倉庫Hierarchy設置進新建立的Logger中
第二種狀況,手動來加載不在類路徑下的配置文件
PropertyConfigurator.configure 執行時會去進行上述的配置文件解析,源碼以下:
public static void configure(java.net.URL configURL) { new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository()); }
仍然先會引起LogManager的類加載,建立出logger倉庫Hierarchy,同時嘗試加載類路徑下的配置文件,此時沒有則不進行解析,此時logger倉庫Hierarchy中的RootLogger默認採用debug級別,沒有appender而已。
而後解析配置文件,對上述logger倉庫Hierarchy的RootLogger進行級別的設置,添加appender
此時再去調用Logger.getLogger,不會致使LogManager的類初始化(由於已經加載過了)
第三種狀況,配置文件在類路徑下,而咱們又手動使用PropertyConfigurator去加載
也就會形成2次加載解析配置文件,僅僅會形成覆蓋而已(對於RootLogger進行重新設置級別,刪除原有的appender,從新加載新的appender),因此屢次加載解析配置文件以最後一次爲準。
##3.3 主要對象總結
LogManager: 它的類加載會建立logger倉庫Hierarchy,並嘗試尋找類路徑下的配置文件,若是有則解析
Hierarchy : 包含三個重要屬性:
PropertyConfigurator: 用於解析log4j.properties文件
Logger : 咱們用來輸出日誌的對象
#4 log4j2
##4.1 背景介紹 log4j2與log4j1發生了很大的變化,不兼容。log4j1僅僅做爲一個實際的日誌框架,slf4j、commons-logging做爲門面,統一各類日誌框架的混亂格局,如今log4j2也想跳出來充當門面了,也想統一你們了。哎,日誌格局愈來愈混亂了。
log4j2分紅2個部分:
##4.2 log4j2的使用案例
###4.2.1 須要的jar包
對應的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配置文件(目前log4j2只支持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是不一樣的。此時Logger是log4j-api中定義的接口,而log4j1中的Logger則是類
##4.3 使用過程簡單分析
不想看源碼的請略過
獲取底層使用的LoggerContextFactory:
一樣LogManager的類加載會去尋找log4j-api定義的LoggerContextFactory接口的底層實現,獲取方式有三種:
若是找到多個,取優先級最高的(該文件中指定了LoggerContextFactory,同時指定了優先級FactoryPriority),如log4j-core-2.2中log4j-provider.properties的文件內容以下:
LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory Log4jAPIVersion = 2.1.0 FactoryPriority= 10
使用LoggerContextFactory獲取LoggerContext
根據LoggerContext獲取Logger
以log4j-core爲例:
##4.4 主要對象總結
LogManager: 它的類加載會去尋找LoggerContextFactory接口的底層實現,會從jar包中的配置文件中尋找,如上面所述
LoggerContextFactory : 用於建立LoggerContext,不一樣的日誌實現系統會有不一樣的實現,如log4j-core中的實現爲Log4jContextFactory
PropertyConfigurator: 用於解析log4j.properties文件
LoggerContext : 它包含了配置信息,並能建立log4j-api定義的Logger接口實例,並緩存這些實例
ConfigurationFactory:上述LoggerContext解析配置文件,須要用到ConfigurationFactory,目前有三個YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory,分別解析yuml json xml形式的配置文件
#5 logback
##5.1 使用案例
###5.1.1 須要的jar包
對應的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); }
補充:
官方使用方式,其實就和slf4j集成了起來
上述的Logger、LoggerFactory都是slf4j本身的接口與類
沒有配置文件的狀況下,使用的是默認配置。搜尋配置文件的過程以下:
- 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.
也能夠在類路徑下加上一個相似以下的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則會去解析對應的配置文件。
##5.3 使用過程簡單分析
不想看源碼的請略過
slf4j與底層的日誌系統進行綁定
在jar包中尋找org/slf4j/impl/StaticLoggerBinder.class 這個類,如在logback-classic中就含有這個類,以下圖所示
若是找到多個StaticLoggerBinder,則代表目前底層有多個實際的日誌框架,slf4j會隨機選擇一個
使用上述找到的StaticLoggerBinder建立一個實例,並返回一個ILoggerFactory實例:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
以logback-classic中的StaticLoggerBinder爲例,在StaticLoggerBinder.getSingleton()過程當中:會去加載解析配置文件 源碼以下:
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) { ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this); //尋找logback.configurationFile的系統屬性 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); }
目前路徑都是定死的,只有logback.configurationFile的系統屬性是能夠更改的,因此若是咱們想更改配置文件的位置(不想放在類路徑下),則須要設置這個系統屬性:
System.setProperty("logback.configurationFile", "/path/to/config.xml");
解析完配置文件後,返回的ILoggerFactory實例的類型是LoggerContext(它包含了配置信息)
根據返回的ILoggerFactory實例,來獲取Logger
就是根據上述的LoggerContext來建立一個Logger,每一個logger與LoggerContext創建了關係,並放到LoggerContext的緩存中,就是LoggerContext的以下屬性:
private Map<String, Logger> loggerCache;
其實上述過程就是slf4j與其餘日誌系統的綁定過程。不一樣的日誌系統與slf4j集成,都會有一個StaticLoggerBinder類,並會擁有一個ILoggerFactory的實現。
#6未完待續
這一篇文章簡單介紹了幾種日誌的簡單用法,下一篇文章就來介紹他們與commons-logging和slf4j怎麼集成起來,就是要弄清楚各類集成jar包作了哪些事情
歡迎關注微信公衆號:乒乓狂魔