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

 2023-10-07 阅读 23 评论 0

摘要:2019獨角獸企業重金招聘Python工程師標準>>> #0 系列目錄# 2種日志接口框架,4種日志實現框架jdk-logging、log4j、logback日志介紹及原理jcl與jul、log4j1、log4j2、logback的集成原理slf4j與jul、log4j1、log4j2、logback的集成原理slf4j、jcl、jul、log4j1

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

#0 系列目錄#

  • 2種日志接口框架,4種日志實現框架
  • jdk-logging、log4j、logback日志介紹及原理
  • jcl與jul、log4j1、log4j2、logback的集成原理
  • slf4j與jul、log4j1、log4j2、logback的集成原理
  • slf4j、jcl、jul、log4j1、log4j2、logback大總結
  • slf4j + log4j原理實現及源碼分析
  • Apache Log4j
  • Log4j架構分析與實戰
  • Log4J配置文件詳解
  • Apache Commons Logging
  • Commons Logging 架構分析
  • JDK Logging
  • JDK Logging 深入分析
  • Logback
  • 從Log4j遷移到LogBack的理由
  • Logback 深入分析
  • Apache Log4j2
  • Log4j2架構分析與實戰
  • Log4j2配置文件詳解

log4j slf4j。#1 Slf4j# ##1.1 介紹## SLF4J,即簡單日志門面(Simple Logging Facade for Java)。從設計模式的角度考慮,它是用來在log和代碼層之間起到門面的作用。對用戶來說只要使用slf4j提供的接口,即可隱藏日志的具體實現。這與jdbc和相似。使用jdbc也就避免了不同的具體數據庫。使用了slf4j可以對客戶端應用解耦。因為當我們在代碼實現中引入log日志的時候,用的是接口,所以可以實時的更具情況來調換具體的日志實現類。這就是slf4j的作用。

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

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

slf4j注解log報錯、##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是怎么實現日志綁定的?

##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);}

log4j2和log4j、上述獲取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 iterationSet<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.java

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=106return TEMP_FACTORY;}throw new IllegalStateException("Unreachable code");}

slf4j-log4j12,所以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();
elselog4jLogger = 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. 成功初始化,則直接獲得具體日志的LoggerFactoryreturn 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=106return 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 iterationSet<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 loggerMapsynchronized (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 pathurl = 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性能優化。

轉載于:https://my.oschina.net/xianggao/blog/519199

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/123502.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息