SLF4J、Log4j、日志框架众

1. SLF4J

简单日志门面(simple logging Facade for java, SLF4J)为各种日志框架提供了统一的接口封装,包括java.util.logging,、logback、以及Log4j等,使得最终用户能够在部署的时候灵活配置自己希望的Loging APIs实现。在应用开发中,需要统一按照SLF4J的API进行开发,在部署时,选择不同的日志系统包加入到JAVA CLASSPATH中,即可自动转换到不同的日志框架上。SLF4J隐藏了具体的转换、适配细节,将应用和具体日志框架解耦开来,如果在类路径中没有发现绑定的日志实现,SLF4J默认使用NOP实现。

1.1 Hello World

将slf4j-api-1.7.21.jar加入类路径,使用SLF4J API输出日志消息,具体代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(HelloWorld.class);
        logger.info("Hello World");
    }
}

编译并运行,结果如下(没有在类路径下找到StaticLoggerBinder实现,默认使用NOP):

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

将slf4j-simple-1.7.21.jar加入类路径(包含slf4j-api-1.7.21.jar、slf4j-simple-1.7.21.jar),编译并执行上面的代码:

0 [main] INFO HelloWorld - Hello World

1.2 经典应用DEMO

如果要切换底层的日志框架,只需要替换SLF4J的日志桥接Adapter即可,比如从 java.util.logging转换到Log4j的话,只需要将slf4j-jdk14-1.7.21.jar替换到slf4j-log4j12-1.7.21.jar。slf4j-api-1.7.21.jar是一直要有的,应用要使用SLF4J的接口嘛。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Wombat {
    // 使用slf4j接口获取日志记录器等
    final Logger logger = LoggerFactory.getLogger(Wombat.class);
    Integer t;
    Integer oldT;

    public void setTemperature(Integer temperature) {
        oldT = t;        
        t = temperature;
        //slf4j接口日志记录器支持{}占位符语法,会提前判断日志级别满足与否,避免无谓字符串拼接
        logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);
        if(temperature.intValue() > 50) {
            logger.info("Temperature has risen above 50 degrees.");
        }
    }
} 

SLF4J默认提供的几个桥接支持有:

  • slf4j-log4j12-1.7.21.jar 支持log4j 1.2系列版本
  • slf4j-jdk14-1.7.21.jar 支持 java.util.logging
  • slf4j-nop-1.7.21.jar 支持NOP,也即是丢弃所有日志消息
  • slf4j-simple-1.7.21.jar 一个SimpleLogger简单实现,日志消息输出到System.err,只有包括INFO级别之上才会输出
  • slf4j-jcl-1.7.21.jar 支持Jakarta Commons Logging
  • logback-classic-1.0.13.jar (requires logback-core-1.0.13.jar) logback是slf4j接口的原生实现
Logback's ch.qos.logback.classic.Logger class is a direct implementation of SLF4J's org.slf4j.Logger interface. Thus, using SLF4J in conjunction with logback involves strictly zero memory and computational overhead.

2. SLF4J源码

SLF4J并没有依赖特殊的类加载机制,SLF4J提供的几个桥接Adapter都是硬编码绑定好具体日志框架的,具体使用时将桥接jar放入类加载路径,注意不要将多个桥接都放入类加载路径中。应用、SLF4J、桥接、具体日志框架直接的关系可以使用下图展示:



SLF4j体系整体的代码量很少,slf4j-log4j12-1.7.2.jar部分是针对Log4j日志框架的适配器、slf4j-api-1.7.2.jar是SLF4J的API、log4j-1.2.17.jar是具体日志框架实现(不作为SLF4J的一部分),如图下所示:

SLF4J

 

先给部分结论如下:

  • LoggerFactoryBinder用于帮助获取具体日志框架的日志工厂ILoggerFactory 实现,每个日志框架都有LoggerFactoryBinder对应的实现StaticLoggerBinder,类全路径名都是org/slf4j/impl/StaticLoggerBinder.class
  • ILoggerFactory是一个日志记录器工厂,用于获取指定名称的日志记录器,每个日志框架桥接都有其对应的实现,返回对应日志框架的日志记录器,如Log4j对应的实现为org.slf4j.impl.Log4jLoggerFactory
  • org.slf4j.Logger接口是slf4j提供的标准日志记录器接口,定义了通用的日志记录器功能。需要使用桥接器将将对org.slf4j.Logger的调用转换到对应日志框架中。以Log4j为例,org.slf4j.impl.Log4jLoggerAdapter实现了org.slf4j.Logger接口,并将日志转发到org.apache.log4j.Logger处理
  • 应用使用slf4j api最主要使用org.slf4j.LoggerFactory工具类,获取org.slf4j.Logger接口对应实现的日志记录器(内部依赖了ILoggerFactory),使用org.slf4j.Logger接口规定的方法进行日志操作
  • org.slf4j.MDC 定义了通用的日志MDC接口,具体原理和Logger是一致的
  • org.slf4j.MDC是MDC功能的入口,作为静态方法,提供了MDC通用的功能,如put、get、remove、clear等功能,具体实现依赖于桥接器MDCAdapter

2.1 org.slf4j.Logger接口

public interface Logger {
  public String getName();

  public boolean isDebugEnabled();
  public void debug(String msg);
  public void debug(String format, Object arg);
  public void debug(String format, Object arg1, Object arg2);
  public void debug(String format, Object... arguments);
  public void debug(String msg, Throwable t);
  public boolean isDebugEnabled(Marker marker);
  public void debug(Marker marker, String msg);
  public void debug(Marker marker, String format, Object arg);
  public void debug(Marker marker, String format, Object arg1, Object arg2);
  public void debug(Marker marker, String format, Object... arguments);
  public void debug(Marker marker, String msg, Throwable t);


  //其他系列接口还有:
  //trace、info、warn、error等
}

org.slf4j.helpers包下的抽象类NamedLoggerBase实现了Logger名字相关的接口即getName逻辑。org.slf4j.helpers包下的抽象类MarkerIgnoringBase又继承自NamedLoggerBase,处理Marker相关的重载,具体是忽略Marker信息,用于适配某些如Log4j不支持Marker的日志框架。具体:

  public void info(Marker marker, String format, Object arg) {
    info(format, arg);
  }

  public void info(Marker marker, String format, Object arg1, Object arg2) {
    info(format, arg1, arg2);
  }

  public void info(Marker marker, String format, Object... arguments) {
    info(format, arguments);
  }

MarkerIgnoringBase介绍2个子类:

  • Log4jLoggerAdapter
  • NOPLogger

2.1.1 Log4jLoggerAdapter

Log4jLoggerAdapter是对Log4j日志记录器Logger的适配器和封装器,Log4jLoggerAdapter作为org.slf4j.Logger接口的实现类,其相关接口都委托给org.apache.log4j.Logger相应的逻辑。具体如:

public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements
    LocationAwareLogger, Serializable {
  final transient org.apache.log4j.Logger logger;
  Log4jLoggerAdapter(org.apache.log4j.Logger logger) {
    this.logger = logger;
    this.name = logger.getName();
    traceCapable = isTraceCapable();
  }

  public boolean isDebugEnabled() {
    return logger.isDebugEnabled();
  }

  public void debug(String msg) {
    logger.log(FQCN, Level.DEBUG, msg, null);
  }

  public void debug(String format, Object arg) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.format(format, arg);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  }

  public void debug(String format, Object arg1, Object arg2) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  }

  public void debug(String format, Object... arguments) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  }

  public void debug(String msg, Throwable t) {
    logger.log(FQCN, Level.DEBUG, msg, t);
  }
/*
  注意,这里一个日志级别的系列函数中,有可变参数Object... arguments也有
  Object arg、Object arg1, Object arg2等重载,是处于性能考虑,可变参数情况下,
  及时日志级别不满足不需要输出日志消息,仍然隐含有Object数组的创建,增加1个、2个Object形参的重载,
  可以避免这类的隐式数组创建,提高性能。
  诸如此类的...
*/
}

2.1.2 NOPLogger

NOPLogger是A direct NOP (no operation) implementation of org.slf4j.Logger

public class NOPLogger extends MarkerIgnoringBase {
  public static final NOPLogger NOP_LOGGER = new NOPLogger();
  protected NOPLogger() {
  }
  public String getName() {
    return "NOP";
  }

  final public boolean isTraceEnabled() {
    return false;
  }
  /** A NOP implementation. */
  final public void trace(String msg) {
    // NOP
  }
  /** A NOP implementation.  */
  final public void trace(String format, Object arg) {
    // NOP
  }
  /** A NOP implementation.  */
  public final void trace(String format, Object arg1, Object arg2) {
    // NOP
  }
  /** A NOP implementation.  */
  public final void trace(String format, Object... argArray) {
    // NOP
  }
  
  /** A NOP implementation. */
  final public void trace(String msg, Throwable t) {
    // NOP
  }

  //诸如此类的...
}

继承关系:

org.slf4j.impl.Log4jLoggerAdapter->org.slf4j.helpers.MarkerIgnoringBase->org.slf4j.helpers.NamedLoggerBase->org.slf4j.Logger

其他日志框架的Logger适配器实现原理也是类似的。

2.2 org.slf4j.LoggerFactory工具类

public final class LoggerFactory {
  // 获取指定名称的日志记录器,具体依赖ILoggerFactory 
  public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
  }

  // 按照类名获取日志记录器
  public static Logger getLogger(Class clazz) {
    return getLogger(clazz.getName());
  }

  // 获取ILoggerFactory工厂,如果状态为未初始化则在类加载路径中,加载类StaticLoggerBinder,进而获取ILoggerFactory实例
  // slf4j提供的针对每一种日志框架的适配器jar包中,都有一个对应的StaticLoggerBinder实现,类名、包名都一直,所以在类路径中只放一份日志框架的适配器jar
  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绑定具体日志框架适配器逻辑, 静态绑定全限定名
  private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
  // 获取类资源
  ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
  paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
  或者
  paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
  jvm加载类获取绑定:
  StaticLoggerBinder.getSingleton();
*/
  // bind details 
  private final static void bind() {
    try {
      Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
      reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
      // the next line does the binding
      StaticLoggerBinder.getSingleton();
      INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
      reportActualBinding(staticLoggerBinderPathSet);
      emitSubstituteLoggerWarning();
    } 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);
    }
  }
}

LoggerFactoryBinder接口是slf4j内部使用的,用于帮助org.slf4j.LoggerFactory获取相应的LoggerFactory工厂即ILoggerFactory实例。

public interface LoggerFactoryBinder {
  // 获取工厂ILoggerFactory 
  public ILoggerFactory getLoggerFactory();
  // 获取工厂实现类名
  public String getLoggerFactoryClassStr();
}

以Log4j桥接jar包中的实现为例,slf4j-log4j12.1.7.2.jar中org.slf4j.impl.StaticLoggerBinder实现如下:

public class StaticLoggerBinder implements LoggerFactoryBinder {

  private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
  private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();

  public static final StaticLoggerBinder getSingleton() {
    return SINGLETON;
  }

  private final ILoggerFactory loggerFactory;

  private StaticLoggerBinder() {
    loggerFactory = new Log4jLoggerFactory();
    try {
      Level level = Level.TRACE;
    } catch (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");
    }
  }
  public ILoggerFactory getLoggerFactory() {
    return loggerFactory;
  }

  public String getLoggerFactoryClassStr() {
    return loggerFactoryClassStr;
  }
}

ILoggerFactory接口

public interface ILoggerFactory {
     // 日志记录器工厂,用于获取日志记录器,会由LoggerFactoryBinder帮助绑定并获取ILoggerFactory 
  public Logger getLogger(String name);
}

以Log4j桥接jar包中的实现为例,slf4j-log4j12.1.7.2.jar中的org.slf4j.impl.Log4jLoggerFactory实现,具体是用一个Map缓存日志记录器,如果Map中不存在指定名的日志记录器,使用Log4j日志框架的LogManager创建一个日志记录器,也一并维护在Map中,如下:

public class Log4jLoggerFactory implements ILoggerFactory {
  // key: name (String), value: a Log4jLoggerAdapter;
  Map loggerMap;

  public Log4jLoggerFactory() {
    loggerMap = new HashMap();
  }
  public Logger getLogger(String name) {
    Logger slf4jLogger = null;
    // protect against concurrent access of loggerMap
    synchronized (this) {
        slf4jLogger = (Logger) loggerMap.get(name);
      if (slf4jLogger == null) {
        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;
  }
}

2.3. MDC&Marker

SLF4J定义了MDC适配器接口org.slf4j.spi.MDCAdapter,每一种日志框架,都有对应的MDC桥接,Log4jMDCAdapter是Log4j的一种MDCAdapter适配器实现,具体是转发到 org.apache.log4j.MDC来处理的。接口如下:

public interface MDCAdapter {
  public void put(String key, String val);
  public String get(String key);
  public void remove(String key);
  public void clear();
  public Map getCopyOfContextMap();
  public void setContextMap(Map contextMap);
}

SLF4J增加了Marker,Marker是有名字属性的对象,用来丰富日志日志功能,有具体的日志框架决定Marker信息具体如何处理,并且多个Marker直接可以有引用父子关系。但是目前只有logback对其进行了实现,其他日志框架都是忽略Marker信息。Marker系列和Logger、LoggerFactory系列原理也是高度一致的,涉及的类有:

  • org.slf4j.Marker
  • org.slf4j.helpers.BasicMarker
  • org.slf4j.MarkerFactory
  • org.slf4j.IMarkerFactory
  • org.slf4j.spi.MarkerFactoryBinder
  • org.slf4j.helpers.BasicMarkerFactory
  • org.slf4j.impl.StaticMarkerBinder

3. 系列总结

3.1 Log4j



作者Ceki Gülcü,Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务 器、NT的事件记录器、UNIX Syslog守护进程等;用户也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个 配置文件来灵活地进行配置,而不需要修改程序代码。doc&wiki: http://people.apache.org/~carnold/log4j/docs/x/logging-log4j-1.2.10/docs/index.html

3.2 Logback

作者Ceki Gülcü,Logback是Log4j的改进版本,而且原生支持SLF4J,由于Logback原生支持SLF4J,因此Logback+SLF4J的组合是日志框架的最佳选择,比SLF4J+其它日志框架的组合要快一些。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是Log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如Log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日记的功能。

3.3 java util logger,JUL

JUL是在JDK 1.4 版本之后开始提供的日志的框架 。由于属于JDK自带的日志框架,import java.util.logging.*即可使用,不需要引用第三方jar包。可是由于Log4j出现的太早了,已经抢占了大部分市场,JUL也只好既生瑜何生亮。

3.4 SLF4J



作者Ceki Gülcü,简单日志门面(simple logging Facade for java, SLF4J)为各种日志框架提供了统一的接口封装,包括java.util.logging,、logback、以及Log4j,使得最终用户能够在部署的时候灵活配置自己希望的Loging APIs实现。在应用开发中,需要统一按照slf4j的API进行开发,在部署时,选择不同的日志系统包加入到JAVA CLASSPATH中,即可自动转换到不同的日志框架上,属于静态编译绑定。SLF4J隐藏了具体的转换、适配细节,将应用和具体日志框架解耦开来,如果在类路径中没有发现绑定的日志实现,SLF4J默认使用NOP实现。
doc&wiki: http://www.slf4j.org/index.html

3.5 Apache Commons Logging,JCL



Apache为了让众多的日志工具有一个相同操作方式,实现做了一个通用日志工具包commons-logging,日志记录时使用commons-logging api,由commons-logging在运行时决定具体使用哪种日志框架。具体方向上和slf4j类似,但使用了ClassLoader寻找和载入底层的日志库,属于运行时动态绑定。和SLF4J选型时主要关注下静态、动态类加载绑定方面的区别。

总的来说的话,优选经典日志组合:LogBack+SLF4J

诸如此类的...

标签: none
评论列表
  1. 你好站长,我想转走您写的这篇文章内容,叨教您赞成吗?我会保存原文出处的链接和作者。
    ca88亚洲城 http://jwc.hbust.com.cn

    1. Trafalgar

      可以的,请知晓!

添加新评论