Java日志框架:SLF4J详解

3,898

SLF4J概述

The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks, such as java.util.logging, logback and log4j. SLF4J allows the end-user to plug in the desired logging framework at deployment time.

Simple Logging Facade for Java(SLF4J)可用作各种日志框架的简单外观或抽象,例如java.util.logginglogbacklog4jSLF4J允许最终用户在部署时插入所需的日志记录框架。

Binding with a logging framework at deployment time

The SLF4J distribution ships with several jar files referred to as SLF4J bindings, with each binding corresponding to a supported framework.

  • slf4j-log4j12-1.7.27.jar

    Binding for log4j version 1.2, a widely used logging framework. You also need to place log4j.jar on your class path.

  • slf4j-jdk14-1.7.27.jar

    Binding for java.util.logging, also referred to as JDK 1.4 logging

  • slf4j-nop-1.7.27.jar

    Binding for NOP, silently discarding all logging.

  • slf4j-simple-1.7.27.jar

    Binding for Simple implementation, which outputs all events to System.err. Only messages of level INFO and higher are printed. This binding may be useful in the context of small applications.

  • slf4j-jcl-1.7.27.jar

    Binding for Jakarta Commons Logging. This binding will delegate all SLF4J logging to JCL.

  • logback-classic-1.2.3.jar (requires logback-core-1.2.3.jar)

    There are also SLF4J bindings external to the SLF4J project, Logback's ch.qos.logback.classic.Loggerclass is a direct implementation of SLF4J's org.slf4j.Logger interface.

To switch logging frameworks, just replace slf4j bindings on your class path. For example, to switch from java.util.logging to log4j, just replace slf4j-jdk14-1.7.27.jar with slf4j-log4j12-1.7.27.jar.

要切换日志框架,只需替换类路径上的slf4j绑定。例如,要从java.util.logging切换到log4j,只需将slf4j-jdk14-1.7.27.jar替换为slf4j-log4j12-1.7.27.jar即可。

SLF4J does not rely on any special class loader machinery. In fact, each SLF4J binding is hardwired at compile time to use one and only one specific logging framework. For example, the slf4j-log4j12-1.7.27.jar binding is bound at compile time to use log4j. In your code, in addition to slf4j-api-1.7.27.jar, you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path.

Consolidate logging via SLF4J(通过SLF4J整合日志记录)

SLF4J caters for this common use-case by providing bridging modules for JCL, java.util.logging and log4j.

SLF4J通过为JCL,java.util.logging和log4j提供桥接模块来满足这种常见用例。

Mapped Diagnostic Context (MDC) support

Mapped Diagnostic Context is essentially a map maintained by the logging framework where the application code provides key-value pairs which can then be inserted by the logging framework in log messages. MDC data can also be highly helpful in filtering messages or triggering certain actions.

映射诊断上下文本质上是由日志框架维护的映射,其中应用程序代码提供键值对,然后日志消息中的日志记录框架可以插入键值对。 MDC数据在过滤消息或触发某些操作方面也非常有用。

SLF4J supports MDC, or mapped diagnostic context. If the underlying logging framework offers MDC functionality, then SLF4J will delegate to the underlying framework's MDC. Note that at this time, only log4j and logback offer MDC functionality. If the underlying framework does not offer MDC, for example java.util.logging, then SLF4J will still store MDC data but the information therein will need to be retrieved by custom user code.

SLF4J支持MDC或映射诊断上下文。 如果底层日志记录框架提供了MDC功能,那么SLF4J将委托给底层框架的MDC。 请注意,目前只有log4j和logback提供MDC功能。 如果底层框架不提供MDC,例如java.util.logging,则SLF4J仍将存储MDC数据,但其中的信息需要由自定义用户代码检索。

原理分析

通过上图可知SLF4J能够抽象各种具体日志框架,是由StaticLoggerBinder类完成。接下来重点看看bind()方法是如何查找StaticLoggerBinder类,源码如下:

private final static void bind() {
    try {
        // 查找类路径下所有的StaticLoggerBinder类
        Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
        // 如果存在多个StaticLoggerBinder类,则打印日志
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        // 获取StaticLoggerBinder实例,如果不存在,则抛出NoClassDefFoundError异常
        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);
    }
}

private static String  STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

private static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    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;
}

SLF4J与logback集成

  1. maven依赖包如下:

    <!-- slf4j-api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.12</version>
    </dependency>
    <!-- logback -->
    <dependency> 
        <groupId>ch.qos.logback</groupId> 
        <artifactId>logback-core</artifactId> 
        <version>1.1.3</version> 
    </dependency>
    <!-- logback-classic(已含有对slf4j的集成包) --> 
    <dependency> 
        <groupId>ch.qos.logback</groupId> 
        <artifactId>logback-classic</artifactId> 
        <version>1.1.3</version> 
    </dependency>
    
  2. 编写logback的配置文件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>
    
  3. 使用方式

    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.info("slf4j-logback info message");
        }
        if(logger.isTraceEnabled()){
            logger.trace("slf4j-logback trace message");
        }
    }
    
  4. org.slf4j.impl.StaticLoggerBinder类实现


SLF4J与log4集成

  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>
    
  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
    
  3. 使用方式

    private static Logger logger=LoggerFactory.getLogger(Log4j2Slf4jTest.class);
    public static void main(String[] args){
        if(logger.isTraceEnabled()){
            logger.trace("slf4j-log4j2 trace message");
        }
        if(logger.isDebugEnabled()){
            logger.debug("slf4j-log4j2 debug message");
        }
        if(logger.isInfoEnabled()){
            logger.info("slf4j-log4j2 info message");
        }
    }
    
  4. org.slf4j.impl.StaticLoggerBinder类实现


参考资料

jcl与jul、log4j1、log4j2、logback的集成原理