slf4j + log4j原理實現及源碼分析

#0 系列目錄#html

#1 Slf4j# ##1.1 介紹## SLF4J,即簡單日誌門面(Simple Logging Facade for Java)。從設計模式的角度考慮,它是用來在log和代碼層之間起到門面的做用。對用戶來講只要使用slf4j提供的接口,便可隱藏日誌的具體實現。這與jdbc和類似。使用jdbc也就避免了不一樣的具體數據庫。使用了slf4j能夠對客戶端應用解耦。由於當咱們在代碼實現中引入log日誌的時候,用的是接口,因此能夠實時的更具狀況來調換具體的日誌實現類。這就是slf4j的做用。java

SLF4J所提供的核心API是一些接口以及一個LoggerFactory的工廠類。SLF4J提供了統一的記錄日誌的接口,只要按照其提供的方法記錄便可,最終日誌的格式、記錄級別、輸出方式等經過具體日誌系統的配置來實現,所以能夠在應用中靈活切換日誌系統。數據庫

配置SLF4J是很是簡單的一件事,只要將與你打算使用的日誌系統對應的jar包加入到項目中,SLF4J就會自動選擇使用你加入的日誌系統。apache

##1.2 簡單使用##設計模式

/**
 * Slf4j 日誌門面接口 Test
 * @author taomk
 * @version 1.0
 * @since 15-10-15 下午3:39
 */
public class Slf4jFacadeTest {

	private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);

	public static void main(String[] args){
		if(logger.isDebugEnabled()){
			logger.debug("slf4j-log4j debug message");
		}
		if(logger.isInfoEnabled()){
			logger.debug("slf4j-log4j info message");
		}
		if(logger.isTraceEnabled()){
			logger.debug("slf4j-log4j trace message");
		}
	}
}

上述Logger接口、LoggerFactory類都是slf4j本身定義的。那麼,SLF4J是怎麼實現日誌綁定的?api

##1.3 日誌綁定##性能優化

  • 在應用中,經過LoggerFactory類的靜態getLogger()獲取logger,代碼以下:
private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
  • LoggerFactory.getLogger(Slf4jFacadeTest.class)方法的源碼以下:LoggerFactory.java
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

上述獲取Log的過程大體分紅2個階段:服務器

  • 獲取ILoggerFactory的過程 (從字面上理解就是生產Logger的工廠);
  • 根據ILoggerFactory獲取Logger的過程;

下面來詳細說明:架構

  • 1 獲取ILoggerFactory的過程,又能夠分紅3個過程
  • 1.1 從類路徑中尋找org/slf4j/impl/StaticLoggerBinder.class類:LoggerFactory.java
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

    private static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = (URL) paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

若是找到多個,則輸出 Class path contains multiple SLF4J bindings,表示有多個日誌實現與slf4j進行了綁定。下面看下當出現多個StaticLoggerBinder的時候的輸出日誌(簡化了一些內容):LoggerFactory.javaapp

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [slf4j-jdk14-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
  • 1.2 「隨機選取"一個StaticLoggerBinder.class來建立一個單例:
private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            // 打印搜索到的全部StaticLoggerBinder日誌
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            // the next line does the binding 隨機選取綁定
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // 打印最終實際綁定StaticLoggerBinder日誌
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstitutedLoggers();
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }
  • 1.3 根據上述建立的StaticLoggerBinder單例,返回一個ILoggerFactory實例:LoggerFactory.java
public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            INITIALIZATION_STATE = ONGOING_INITIALIZATION;
            performInitialization();
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            // 返回綁定
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
            return TEMP_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }

因此slf4j與其餘實際的日誌框架的集成jar包中,都會含有這樣的一個org/slf4j/impl/StaticLoggerBinder.class類文件,而且提供一個ILoggerFactory的實現。

  • 2 根據ILoggerFactory獲取Logger的過程:這就要看具體的ILoggerFactory類型了,下面與Log4j集成來詳細說明。

#2 Log4j介紹# Apache的一個開放源代碼項目,經過使用Log4j,咱們能夠控制日誌信息輸送的目的地是控制檯、文件、GUI組件、甚至是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等用戶也能夠控制每一條日誌的輸出格式經過定義每一條日誌信息的級別,用戶可以更加細緻地控制日誌的生成過程。這些能夠經過一個配置文件來靈活地進行配置,而不須要修改程序代碼。具體詳細介紹,參見Log4j架構分析與實戰

#3 Slf4j與Log4j集成# ##3.1 Maven依賴##

<!-- slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.12</version>
</dependency>

<!-- slf4j-log4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.12</version>
</dependency>

<!-- log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

##3.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

配置文件的詳細內容參見Log4J配置文件詳解

  • 第二步:代碼中以下使用
private static Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class);
public static void main(String[] args){
    if(logger.isDebugEnabled()){
        logger.debug("slf4j-log4j debug message");
    }
    if(logger.isInfoEnabled()){
        logger.info("slf4j-log4j info message");
    }
    if(logger.isTraceEnabled()){
        logger.trace("slf4j-log4j trace message");
    }
}
  • 補充說明:
  • 1 配置文件一樣能夠隨意放置,如log4j1原生方式加載配置文件的方式log4j1原生開發
  • 2 注意二者方式的不一樣:
slf4j:  Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class);
log4j:  Logger logger=Logger.getLogger(Log4jTest.class);

slf4j的Logger是slf4j定義的接口,而log4j的Logger是類。LoggerFactory是slf4j本身的類。

##3.3 原理分析## 先來看下slf4j-log4j12包中的內容:

輸入圖片說明

  • 的確是有org/slf4j/impl/StaticLoggerBinder.class類。
  • 該StaticLoggerBinder返回的ILoggerFactory類型將會是Log4jLoggerFactory。
  • Log4jLoggerAdapter就是實現了slf4j定義的Logger接口。

來看下具體過程:

  • 1 獲取對應的ILoggerFactory:從上面的slf4j的原理中咱們知道:ILoggerFactory是由StaticLoggerBinder來建立出來的,因此能夠簡單分紅2個過程:
  • 1.1 第一個過程:slf4j尋找綁定類StaticLoggerBinder:使用ClassLoader來加載「org/slf4j/impl/StaticLoggerBinder.class"這樣的類的url,而後就找到了slf4j-log4j12包中的StaticLoggerBinder。
  • 1.2 第二個過程:建立出StaticLoggerBinder實例,並建立出ILoggerFactory,源碼以下:
StaticLoggerBinder.getSingleton().getLoggerFactory();

以slf4j-log4j12中的StaticLoggerBinder爲例,建立出的ILoggerFactory爲Log4jLoggerFactory。

  • 2 根據ILoggerFactory獲取Logger的過程,來看下Log4jLoggerFactory是如何返回一個slf4j定義的Logger接口的實例的,源碼以下:
org.apache.log4j.Logger log4jLogger;
if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
    log4jLogger = LogManager.getRootLogger();
else
    log4jLogger = LogManager.getLogger(name);
Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
  • 2.1 咱們能夠看到是經過log4j1的原生方式,即便用log4j1的LogManager來獲取,引起log4j1的加載配置文件,而後初始化,最後返回一個org.apache.log4j.Logger log4jLogger,參見log4j1原生開發
  • 2.2 將上述的org.apache.log4j.Logger log4jLogger封裝成Log4jLoggerAdapter,而Log4jLoggerAdapter是實現了slf4j的接口,因此咱們使用的slf4j的Logger接口實例(這裏即Log4jLoggerAdapter)都會委託給內部的org.apache.log4j.Logger實例。

#4 Slf4j與Log4j源碼分析# ##4.1 Slf4j初始化##

  • 初始化時機:下面這行代碼,會在當前類類加載時或者顯示得執行調用LoggerFactory.getLogger()方法時,觸發Slf4j初始化,並綁定具體日誌:Slf4jFacadeTest.java
private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
  • 初始化步驟:
    1. 源碼1:LoggerFactory.java
public static Logger getLogger(String name) {
        // 1. 初始化LoggerFactory,綁定具體日誌,得到具體日誌的LoggerFactory。
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        // 2. 根據具體日誌的LoggerFactory,觸發具體日誌的初始化並得到具體日誌的Logger對象;
        return iLoggerFactory.getLogger(name);
    }
  1. 源碼2:LoggerFactory.java
public static ILoggerFactory getILoggerFactory() {
        // 1. 是否已經初始化了,不然進行初始化
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            INITIALIZATION_STATE = ONGOING_INITIALIZATION;
            performInitialization();
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            // 2. 成功初始化,則直接得到具體日誌的LoggerFactory
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
            return TEMP_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }
  1. 源碼3:LoggerFactory.java
private final static void performInitialization() {
        // 1. 綁定具體的日誌實現
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }
  1. 源碼4:LoggerFactory.java
private final static void bind() {
        try {
            // 1. 掃描查找「org/slf4j/impl/StaticLoggerBinder.class」
            Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            // 2. 打印找到多個「org/slf4j/impl/StaticLoggerBinder.class」的日誌
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            // the next line does the binding 隨機選擇綁定,類加載器隨機選擇。
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // 3. 打印綁定具體org/slf4j/impl/StaticLoggerBinder.class的日誌
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstitutedLoggers();
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }
  1. 源碼5:LoggerFactory.java
private static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                // 1. 掃包查找「org/slf4j/impl/StaticLoggerBinder.class」
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = (URL) paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }
  1. 源碼6:slf4j-log4j-1.6.2.jar中StaticLoggerBinder.java
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    public static final StaticLoggerBinder getSingleton() {
        // 1. 返回StaticLoggerBinder實例
        return SINGLETON;
      }
    private StaticLoggerBinder() {
        // 2. StaticLoggerBinder初始化
        loggerFactory = new Log4jLoggerFactory();
        try {
            Level level = Level.TRACE;
        }cache (NoSuchFieldError nsfe) {
            Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
        }
    }

以上就是Slf4j初始化過程的源代碼,其初始化過程就是綁定具體日誌實現

##4.2 Log4j初始化## 這裏只在源碼層級作分析,不想看源碼,可直接參考具體詳細流程,請參見Log4j初始化分析

  • 初始化時機:前一小節Slf4j已提到,在iLoggerFactory.getLogger(name)時觸發Log4j初始化。iLoggerFactory具體類型爲:Log4jLoggerFactory。

  • 初始化步驟:

  1. 源碼1:Log4jLoggerFactory.java
public Logger getLogger(String name) {
    Logger slf4jLogger = null;
    // protect against concurrent access of loggerMap
    synchronized (this) {
        slf4jLogger = (Logger) loggerMap.get(name);
      if (slf4jLogger == null) {
        // 1. 獲取Logej具體的Logger對象。
        org.apache.log4j.Logger log4jLogger;
        if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) {
           log4jLogger = LogManager.getRootLogger();
        } else {
          log4jLogger = LogManager.getLogger(name);
        }
        slf4jLogger = new Log4jLoggerAdapter(log4jLogger);
        loggerMap.put(name, slf4jLogger);
      }
    }
    return slf4jLogger;
  }
  1. 源碼2:LogManager.java靜態代碼塊初始化Log4j
static {
        // By default we use a DefaultRepositorySelector which always returns 'h'.
        // 1. 初始化Logger倉庫,並添加一個RootLogger實例,默認日誌級別爲DEBUG。
        Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
        repositorySelector = new DefaultRepositorySelector(h);
        
        /** Search for the properties file log4j.properties in the CLASSPATH.  */
        String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null);
        // 2. 檢查系統屬性log4j.defaultInitOverride,若是該屬性被設置爲false,則執行初始化;不然(只要不是false,不管是什麼值,甚至沒有值,都是不然),跳過初始化。
        // if there is no default init override, then get the resource
        // specified by the user or the default config file.
        if(override == null || "false".equalsIgnoreCase(override)) {

            String configurationOptionStr = OptionConverter.getSystemProperty(
							  DEFAULT_CONFIGURATION_KEY, 
							  null);

            String configuratorClassName = OptionConverter.getSystemProperty(
                                                   CONFIGURATOR_CLASS_KEY, 
						   null);

            URL url = null;
            
            // 3. 把系統屬性log4j.configuration的值賦給變量resource。若是該系統變量沒有被定義,則把resource賦值爲"log4j.properties"。注意:在apache的log4j文檔中建議使用定義log4j.configuration系統屬性的方法來設置默認的初始化文件是一個好方法。
            // if the user has not specified the log4j.configuration
            // property, we search first for the file "log4j.xml" and then
            // "log4j.properties"
            if(configurationOptionStr == null) {	
                url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
                if(url == null) {
                    url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
                }
            } else {
                try {
                    // 4. 試圖把resource變量轉化成爲一個URL對象url。若是通常的轉化方法行不通,就調用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)方法來完成轉化。
                    url = new URL(configurationOptionStr);
                } catch (MalformedURLException ex) {
                    // so, resource is not a URL:
                    // attempt to get the resource from the class path
                    url = Loader.getResource(configurationOptionStr); 
                }	
            }
      
            // If we have a non-null url, then delegate the rest of the
            // configuration to the OptionConverter.selectAndConfigure
            // method.
            if(url != null) {
                LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
                try {
                    // 5. 若是url以".xml"結尾,則調用方法DOMConfigurator.configure(url)來完成初始化;不然,則調用方法PropertyConfigurator.configure(url)來完成初始化。若是url指定的資源不能被得到,則跳出初始化過程。
                    OptionConverter.selectAndConfigure(url, configuratorClassName,
					   LogManager.getLoggerRepository());
                } catch (NoClassDefFoundError e) {
                    LogLog.warn("Error during default initialization", e);
                }
            } else {
                LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
            }
        } else {
            LogLog.debug("Default initialization of overridden by " + 
            DEFAULT_INIT_OVERRIDE_KEY + "property."); 
        }  
    }
  1. 源碼3:OptionConverter.java
public static void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
        Configurator configurator = null;
        String filename = url.getFile();
        // 1. 若是url以".xml"結尾,則調用方法DOMConfigurator.configure(url)來完成初始化;不然,則調用方法PropertyConfigurator.configure(url)來完成初始化。若是url指定的資源不能被得到,則跳出初始化過程。
        if(clazz == null && filename != null && filename.endsWith(".xml")) {
            clazz = "org.apache.log4j.xml.DOMConfigurator";
        }

        if(clazz != null) {
            LogLog.debug("Preferred configurator class: " + clazz);
            configurator = (Configurator) instantiateByClassName(clazz,
							  Configurator.class,
							  null);
            if(configurator == null) {
                LogLog.error("Could not instantiate configurator ["+clazz+"].");
                return;
            }
        } else {
            configurator = new PropertyConfigurator();
        }

        configurator.doConfigure(url, hierarchy);
    }

##4.3 Log4j輸出日誌## 這裏只在源碼層級作分析,不想看源碼,可直接參考具體詳細流程,請參見Log4j輸出日誌

  • 預處理: 當調用Log4j的方法(如:debug(String, Throwable)、info(String, Throwable))輸出日誌時,首先對日誌信息進行預處理。
  1. 源碼1:Category.java(Logger繼承自Category.java
public void info(Object message) {
        // 1. 根據全局日誌等級threshold進行判斷,若是日誌等級低於threshold,不輸出日誌。
        if(repository.isDisabled(Level.INFO_INT))
            return;
        // 2. 根據當前logger配置的日誌等級level進行判斷,若是日誌等級低於當前logger配置的日誌等級,不輸出日誌。
        if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel()))
            // 3. 將日誌信息封裝成LoggingEvent對象。
            forcedLog(FQCN, Level.INFO, message, null);
    }
  1. 源碼2:Category.java
protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
        // 1. 將LoggingEvent對象分發給全部的Appender。
        callAppenders(new LoggingEvent(fqcn, this, level, message, t));
    }

    public void callAppenders(LoggingEvent event) {
        int writes = 0;

        for(Category c = this; c != null; c=c.parent) {
            // Protected against simultaneous call to addAppender, removeAppender,...
            synchronized(c) {
                if(c.aai != null) {
                    // 2. 將LoggingEvent對象分發給全部的Appender。
                    writes += c.aai.appendLoopOnAppenders(event);
                }
                if(!c.additive) {
                    break;
                }
            }
        }

        if(writes == 0) {
            repository.emitNoAppenderWarning(this);
        }
    }

    public int appendLoopOnAppenders(LoggingEvent event) {
        int size = 0;
        Appender appender;

        if(appenderList != null) {
            size = appenderList.size();
            for(int i = 0; i < size; i++) {
                appender = (Appender) appenderList.elementAt(i);
                appender.doAppend(event);
            }
        }    
        return size;
    }
  • 輸出日誌前還有兩道工序須要處理:Filter處理和日誌信息格式化
  1. 源碼1:
public synchronized void doAppend(LoggingEvent event) {
        if (closed) {
            LogLog.error("Attempted to append to closed appender named ["
                    + name + "].");
            return;
        }

        if (!isAsSevereAsThreshold(event.getLevel())) {
            return;
        }
        
        // Filter處理
        Filter f = this.headFilter;

        FILTER_LOOP: while (f != null) {
            // 1. 有三種返回值 DENY、ACCEPT、NEUTRAL,DENY表示丟棄當前日誌信息,ACCEPT表示輸出當前日誌信息,NEUTRAL表示繼續下一個Filter。Filter只能在XML配置文件中使用,Properties文件中不支持。
            switch (f.decide(event)) {

            case Filter.DENY:
                return;
            case Filter.ACCEPT:
                break FILTER_LOOP;
            case Filter.NEUTRAL:
                f = f.getNext();
            }
        }

        this.append(event);
    }

    public void append(LoggingEvent event) {

        // Reminder: the nesting of calls is:
        //
        //    doAppend()
        //      - check threshold
        //      - filter
        //      - append();
        //        - checkEntryConditions();
        //        - subAppend();

        if(!checkEntryConditions()) {
            return;
        }
        subAppend(event);
    }

    protected void subAppend(LoggingEvent event) {
        // 2. 日誌信息格式化:對日誌進行格式化處理。
        this.qw.write(this.layout.format(event));

        if (layout.ignoresThrowable()) {
            String[] s = event.getThrowableStrRep();
            if (s != null) {
                int len = s.length;
                for (int i = 0; i < len; i++) {
                    this.qw.write(s[i]);
                    this.qw.write(Layout.LINE_SEP);
                }
            }
        }

        if (shouldFlush(event)) {
            // 3. 將日誌信息輸出至目的地(文件、數據庫或網格)
            this.qw.flush();
        }
    }
  1. 若想了解Log4j的日誌異步輸出實現,請參見Log4j性能優化
相關文章
相關標籤/搜索