天天看點

jdk-logging、log4j、logback日志介紹及原理

來源:伯樂線上專欄作者- 乒乓狂魔 

連結:http://blog.jobbole.com/102976/

1 需要解決的疑惑

目前的日志架構有jdk自帶的logging,log4j1、log4j2、logback,目前用于實作日志統一的架構apache的commons-logging、slf4j

為了理清它們的關系,與繁雜的各種內建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 簡單過程分析

不想看源碼的請略過

  • 建立一個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包

  • log4j

maven依賴如下:

    log4jgroupId>

    log4jartifactId>

    1.2.17version>

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");

       }

   }

}

補充:上述方式預設到類路徑下加載log4j.properties配置檔案,如果log4j.properties配置檔案不在類路徑下,則可以選擇如下方式之一來加載配置檔案

1.使用classLoader來加載資源PropertyConfigurator.configure(Log4jTest.class.getClassLoader().getResource(“properties/log4j.properties”));

2.使用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;

  //其他略

}

  • LoggerFactory defaultFactory: 就是建立Logger的工廠
  • Hashtable ht:用來存放上述工廠建立的Logger
  • Logger root:作為根Logger

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 第四步:解析上述配置檔案

如果是xml檔案則org.apache.log4j.xml.DOMConfigurator類來解析

如果是properties檔案,則使用org.apache.log4j.PropertyConfigurator來解析

不再詳細說明解析過程,看下解析後的結果:設定RootLogger的級别

對RootLogger添加一系列我們配置的appender(我們通過logger來輸出日志,通過logger中的appender指明了日志的輸出目的地)

5 第五步:當一切都準備妥當後,就該擷取Logger了

使用logger倉庫Hierarchy中内置的LoggerFactory工廠來建立Logger了,并緩存起來,同時将logger倉庫Hierarchy設定進新建立的Logger中

第二種情況,手動來加載不在類路徑下的配置檔案

PropertyConfigurator.configure 執行時會去進行上述的配置檔案解析,源碼如下:

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 : 包含三個重要屬性:
    • LoggerFactory logger的建立工廠
    • Hashtable 用于存放上述工廠建立的logger
    • Logger root logger,用于承載解析檔案的結果,設定級别,同時存放appender
  • PropertyConfigurator: 用于解析log4j.properties檔案
  • Logger : 我們用來輸出日志的對象

4 log4j2

4.1 背景介紹

log4j2與log4j1發生了很大的變化,不相容。log4j1僅僅作為一個實際的日志架構,slf4j、commons-logging作為門面,統一各種日志架構的混亂格局,現在log4j2也想跳出來充當門面了,也想統一大家了。哎,日志格局越來越混亂了。

log4j2分成2個部分:

  • log4j-api: 作為日志接口層,用于統一底層日志系統
  • log4j-core : 作為上述日志接口的實作,是一個實際的日志架構

4.2 log4j2的使用案例

4.2.1 需要的jar包

  • log4j-api
  • log4j-core

對應的maven依賴是:

    org.apache.logging.log4jgroupId>

    log4j-apiartifactId>

    2.2version>

dependency>

    org.apache.logging.log4jgroupId>

    log4j-coreartifactId>

    2.2version>

  dependency>

4.2.2 使用方式

第一步:編寫log4j2.xml配置檔案(目前log4j2隻支援xml json yuml,不再支援properties檔案)

xml version="1.0" encoding="UTF-8"?>

<Configuration status="WARN"> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> Console> Appenders> <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接口的底層實作,擷取方式有三種:

第一種: 嘗試從jar中尋找log4j2.component.properties檔案,如果配置了log4j2.loggerContextFactory則使用該LoggerContextFactory

第二種:如果沒找到,嘗試從jar包中尋找META-INF/log4j-provider.properties檔案,如log4j-core-2.2中就有該檔案,如下圖所示:、

jdk-logging、log4j、logback日志介紹及原理

如果找到多個,取優先級最高的(該檔案中指定了LoggerContextFactory,同時指定了優先級FactoryPriority),如log4j-core-2.2中log4j-provider.properties的檔案内容如下:

LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory Log4jAPIVersion = 2.1.0 FactoryPriority= 10

第三種情況:上述方式還沒找到,就使用預設的SimpleLoggerContextFactory

使用LoggerContextFactory擷取LoggerContext

根據LoggerContext擷取Logger

以log4j-core為例:

會首先判斷LoggerContext是否被初始化過了,沒有則進行初始化

擷取ConfigurationFactory,從配置中擷取和插件中擷取(log4j-core核心包中有三個YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory)

以上文的案例中,會使用XmlConfigurationFactory來加載log4j2.xml配置檔案

LoggerContext初始化後,就可以擷取或者建立Logger了

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包

  • logback-core
  • logback-classic
  • slf4j-api

對應的maven依賴為:

    ch.qos.logbackgroupId>

    logback-coreartifactId>

    1.1.3version>

dependency>

    ch.qos.logbackgroupId>

    logback-classicartifactId>

    1.1.3version>

dependency>

    org.slf4jgroupId>

    slf4j-apiartifactId>

    1.7.12version>

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自己的接口與類

沒有配置檔案的情況下,使用的是預設配置。搜尋配置檔案的過程如下:

1.Logback tries to find a file called logback.groovy in the classpath.

2.If no such file is found, logback tries to find a file called logback-test.xml in the classpath.

3.If no such file is found, it checks for the file logback.xml in the classpath..

4.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.

5.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"?>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

      %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>

    encoder>

  appender>

  <root level="DEBUG">          

    <appender-ref ref="STDOUT" />

  root>  

configuration>

logback則會去解析對應的配置檔案。

5.3 使用過程簡單分析

不想看源碼的請略過

slf4j與底層的日志系統進行綁定

在jar包中尋找org/slf4j/impl/StaticLoggerBinder.class 這個類,如在logback-classic中就含有這個類,如下圖所示

jdk-logging、log4j、logback日志介紹及原理

如果找到多個StaticLoggerBinder,則表明目前底層有多個實際的日志架構,slf4j會随機選擇一個

使用上述找到的StaticLoggerBinder建立一個執行個體,并傳回一個ILoggerFactory執行個體:

return StaticLoggerBinder.getSingleton().getLoggerFactory();

以logback-classic中的StaticLoggerBinder為例,在StaticLoggerBinder.getSingl

eton()過程中:會去加載解析配置檔案 源碼如下:

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 loggerCache;

其實上述過程就是slf4j與其他日志系統的綁定過程。不同的日志系統與slf4j內建,都會有一個StaticLoggerBinder類,并會擁有一個ILoggerFactory的實作。

6未完待續

這一篇文章簡單介紹了幾種日志的簡單用法,下一篇文章就來介紹他們與commons-logging和slf4j怎麼內建起來,就是要弄清楚各種內建jar包做了哪些事情

繼續閱讀