文档章节

slf4j + log4j原理实现及源码分析

陶邦仁
 陶邦仁
发布于 2015/10/20 10:40
字数 3728
阅读 5467
收藏 19

#0 系列目录#

#1 Slf4j# ##1.1 介绍## SLF4J,即简单日志门面(Simple Logging Facade for Java)。从设计模式的角度考虑,它是用来在log和代码层之间起到门面的作用。对用户来说只要使用slf4j提供的接口,即可隐藏日志的具体实现。这与jdbc和相似。使用jdbc也就避免了不同的具体数据库。使用了slf4j可以对客户端应用解耦。因为当我们在代码实现中引入log日志的时候,用的是接口,所以可以实时的更具情况来调换具体的日志实现类。这就是slf4j的作用。

SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

配置SLF4J是非常简单的一件事,只要将与你打算使用的日志系统对应的jar包加入到项目中,SLF4J就会自动选择使用你加入的日志系统。

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

上述获取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.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=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性能优化

© 著作权归作者所有

共有 人打赏支持
陶邦仁
粉丝 1636
博文 420
码字总数 1483887
作品 0
海淀
技术主管
私信 提问
slf4j与jul、log4j1、log4j2、logback的集成原理

1 系列目录 - jdk-logging、log4j、logback日志介绍及原理- commons-logging与jdk-logging、log4j1、log4j2、logback的集成原理- slf4j与jdk-logging、log4j1、log4j2、logback的集成原理- s...

乒乓狂魔
2015/04/30
0
5
jcl与jul、log4j1、log4j2、logback的集成原理

1 系列目录 - jdk-logging、log4j、logback日志介绍及原理- jcl与jul、log4j1、log4j2、logback的集成原理- slf4j与jdk-logging、log4j1、log4j2、logback的集成原理- slf4j、jcl、jul、log...

乒乓狂魔
2015/04/29
0
2
架构师必备,带你弄清混乱的JAVA日志体系

引言 还在为弄不清 、 、 等日志框架之间复杂的关系而感到烦恼吗? 还在为如何统一系统的日志输出而感到不知所措嘛? 您是否依然存在这样的烦恼。比如,要更改spring的日志输出为log4j 2,却...

微笑向暖wx
2018/12/13
0
0
jdk-logging、log4j、logback日志介绍及原理

系列文章已完成,目录如下: - jdk-logging、log4j、logback日志介绍及原理- commons-logging与jdk-logging、log4j1、log4j2、logback的集成原理- slf4j与jdk-logging、log4j1、log4j2、log...

乒乓狂魔
2015/04/27
0
17
slf4j、jcl、jul、log4j1、log4j2、logback大总结

1 系列目录 - jdk-logging、log4j、logback日志介绍及原理- commons-logging与jdk-logging、log4j1、log4j2、logback的集成原理- slf4j与jdk-logging、log4j1、log4j2、logback的集成原理- s...

乒乓狂魔
2015/05/04
0
33

没有更多内容

加载失败,请刷新页面

加载更多

Android Multimedia框架总结(一)MediaPlayer介绍之状态图及生命周期

前言:从本篇开始,将进入Multimedia框架,包含MediaPlayer, Camera, Surface, MediaRecord, 接下来几篇都是MediaPlayer相关。同样看下Agenda如下: MediaPlayer的状态图 Idle 状态 End 状态...

天王盖地虎626
5分钟前
0
0
IntelliJ IDEA创建第一个Spring boot项目

下载maven:http://maven.apache.org/download.cgi 开发工具:IntelliJ IDEA JDK: Java JDK1.8 ## 1.为了第一个项目初始化速度加快,我们先来配置maven: 添加配置: 选择Build,Execution,Dep...

凌宇之蓝
54分钟前
2
0
Coding and Paper Letter(四十五)

资源整理。 1 Coding: 1.Python库gempy,一种基于Python的开源三维结构地质建模软件,它允许从界面和方向数据隐式(即自动)创建复杂的地质模型。 它还支持随机建模以解决参数和模型的不确定...

胖胖雕
今天
6
0
golang 声明一个指定长度的数组,用于后续添加

很多时候我们需要声明一个指定长度的数组,用于后续添加.在使用go的时候要注意,下面的第一个例子会有报错 "non-constant array bound",应该使用第二个例子. Length 是动态的值 有报错的例子 ...

漫步海边小路
今天
0
0
Java NIO示例

Server端 /** * 《构建高性能的大型分布式Java应用》 * 书中的示例代码 * 版权所有 2008---2009 */package book.chapter1.tcpnio;import java.net.InetSocketAddress;i...

月下狼
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部