一.引言
日志在排除线上问题、跟踪线上系统运行情况中发挥着重要的作用。在java应用的开发中,有许多的日志框架。
这些日志框架大致可以分为两类,一类是日志门面(JCL、slf4j),定义日志的抽象接口。另一类是日志的实现(JUL、log4j、log4j2、logback),负责真正的处理日志。
日志框架的发展的前因后果
为什么会有这么多的日志框架,从Java日志框架的发展史里大概可以一探究竟。
log4j是Java社区最早的日志框架,推出后一度成为Java的事实日志标准,据说Apache曾建议Sun把log4j加入到Java标准库中,但是被Sun拒绝。 在Java1.4中,Sun在标准库中推出了自己的日志框架java.util.logging,功能相对简陋 虽然JUL相对简陋,但还是有类库采用了它,这就出现了同一个项目中同时使用log4j和JUL要维护两套配置的问题,Apache试图解决这个问题,推出了JCL日志门面(接口),定义了一套日志接口,底层实现支持log4j和JUL,但是并没有解决多套配置的问题 log4j的主力开发Ceki Gülcü由于某些原因离开了Apache,创建了slf4j日志门面(接口),并实现了性能比log4j性能更好的logback(如果Ceki Gülcü没有离开Apache,这应该就是log4j2的codebase了) Apache不甘示弱,成立了不兼容log4j 1.x的log4j2项目,引入了logback的特性(还酸酸地说解决了logback架构上存在的问题),但目前采用率不是很高
日志框架的选择
那么面对这些日志框架,该如何选择呢?如果你是在开发一个新的项目(类库)而不是维护一个上古的遗留代码,那么在打印日志时推荐使用日志门面,秉承面向接口编程的思想,与具体的日志实现框架解耦,这样日后可以很容易地切换到其他的日志实现框架。
特别是当你的代码以SDK的方式提供给别人使用时,使用日志门面能避免使用方可能出现的日志框架冲突问题。如果你的SDK里使用了log4j,而使用方的应用里使用的logback,这时使用方就不得不分别针对log4j和logback维护两套日志配置文件,来确保所有日志正常的输出。
在目前已有的两个日志门面框架中,slf4j规避了JCL在部分场景下因为ClassLoader导致绑定日志实现框架失败的问题;能支持以上提到的所有日志实现框架;且slf4j支持占位符功能,在需要拼接日志的情况在接口层面就比JCL有更好的性能,所以推荐使用slf4j,下面简单多介绍下slf4j。
// slf4j的占位符功能 LOGGER.info("hello {}", name);
二.slf4j-api
slf4j-api为各种日志框架提供了一个统一的接口,使用户可以用统一的接口来记录日志,但是可以动态的决定真正的实现框架。logback、log4j、common-logging等都实现了这个接口。所以阅读logback源码之前,首先看看logback是如何与slf4j对接的。
slf4j-api最重要的是三个接口和一个入口类。
两个接口分别是org.slf4j.ILoggerFactory、org.slf4j.Logger和org.slf4j.spi.LoggerFactoryBinder。
入口类是org.slf4j.LoggerFactory。
1.org.slf4j.ILoggerFactory接口只提供了一个获取Logger的动作,由具体的日志框架实现者去实现该接口。
/** * ILoggerFactory logger工厂接口 * 提供获取Logger动作 * 由具体的日志框架实现者去实现该接口 */ public interface ILoggerFactory { /** * 获取Logger * * @param name the name of the Logger to return * @return a Logger instance */ public Logger getLogger(String name); }
2.org.slf4j.Logger接口提供了打印不同等级日志的动作和获取loggerName等动作。
3.org.slf4j.spi.LoggerFactoryBinder接口提供了获取ILoggerFactory的实现类的动作。一个内部接口去帮助LoggerFactory绑定合适的ILoggerFactory的实例。
由指定路径的实现类org.slf4j.impl.StaticLoggerBinder 去实现该接口,每一个实现slf4j门面的日志框架都有一个实现该接口的org.slf4j.impl.StaticLoggerBinder。
public interface LoggerFactoryBinder { public ILoggerFactory getLoggerFactory(); public String getLoggerFactoryClassStr(); }
4..org.slf4j.LoggerFactory是一个入口类,通过调用ILoggerFactory的实现类获取Logger对象,稍后我们分析LoggerFactory的源码。
首先看一个简单的例子:
public class HelloWorld1 { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1"); logger.debug("Hello world."); } }
不管实现框架是什么,要获取Logger对象,都是通过这个LoggerFactory的getLogger()方法,所以这个类也非常重要。具体实现框架和slf4j的对接,就是通过这个类LoggerFactory,让我们来看看这个类是如何对接slf4j的。
getLogger(String name)
1.获取ILoggerFactory的实现类,并调用getLogger(name)方法返回Logger对象。
public static Logger getLogger(String name) { //获得ILoggerFactory的实现类 //把获取实际Logger的工作,委托给具体的日志框架上,比如log4j、logback、common-logging等 ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); }
getILoggerFactory()
1.首先用一个静态变量INITIALIZATION_STATE标识初始化状态,初始值=0,用双重检查锁判断这个状态,如果没有初始化,则将状态置为1,并且调用performInitialization()方法去初始化。
2.当初始化状态=1,则调用StaticLoggerBinder.getSingleton().getLoggerFactory()获取ILoggerFactory接口的实现类。
当初始化状态=4,则意味着没有可绑定的实现框架,返回一个空的ILoggerFactory接口实现类NOPLoggerFactory。内部getLogger(name)方法返回一个NOPLogger对象,其中打印方法中什么也没做。
当初始化状态=2,则初始化过程中发生异常,直接抛出IllegalStateException()异常。
当初始化状态=3,则正在进行初始化,返回一个替代工厂SubstituteLoggerFactory。
static final int UNINITIALIZED = 0; //未初始化 static final int ONGOING_INITIALIZATION = 1; //正在初始化 static final int FAILED_INITIALIZATION = 2; //失败初始化 static final int SUCCESSFUL_INITIALIZATION = 3; //成功初始化 static final int NOP_FALLBACK_INITIALIZATION = 4; //空初始化 static volatile int INITIALIZATION_STATE = UNINITIALIZED; //初始化状态
static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory(); //替代工厂 static final NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory(); //空工厂
/** * Return the {@link ILoggerFactory} instance in use. * <p/> * <p/> * ILoggerFactory instance is bound with this class at compile time. * ILoggerFactory 实例在编译时期和该类绑定 * @return the ILoggerFactory instance in use */ public static ILoggerFactory getILoggerFactory() { //判断是否初始化 //双重检查锁 并发情况下重复执行初始化 if (INITIALIZATION_STATE == UNINITIALIZED) {//若已经初始化则无需竞争锁 提高性能 synchronized (LoggerFactory.class) { 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://jira.qos.ch/browse/SLF4J-97 return SUBST_FACTORY; } throw new IllegalStateException("Unreachable code"); }
performInitialization()
1.调用bind()执行绑定。
2.如果初始化成功,则进行版本检查。
private final static void performInitialization() { //执行绑定 bind(); //成功获取binder实现 if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { //这里会获取binder的静态变量REQUESTED_API_VERSION(要求API的版本号)和slf4j做比较 versionSanityCheck(); } }
bind()
1.首先是检查classpath里是否存在多个日志框架,如果有就会抛出警告信息,提示用户只保留一个。
2.这段代码提到了一个最关键的StaticLoggerBinder类,这个类实现LoggerFactoryBinder接口,提供获取ILoggerFactory实现类的方法,检查是否有这个类存在,以及这个类有没有getSingleton()方法,如果有,就视为绑定成功。其实这个类还必须有getLoggerFactory()方法,否则虽然绑定成功,但是到了运行期,一样会抛出NoSuchMethodException。我认为这里是slf4j设计不好的地方,应该在bind()方法里,就检查一下StaticLoggerBinder有没有实现getLoggerFactory()方法。
private final static void bind() { try { Set<URL> staticLoggerBinderPathSet = null; // skip check under android, see also // http://jira.qos.ch/browse/SLF4J-328 //判断系统变量java.vendor.url是否包含android 默认是不会包含的 if (!isAndroid()) { //在classpath下查找org/slf4j/impl/StaticLoggerBinder.class staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); //检查是否找到多个binder reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); } // the next line does the binding //饿汉单例 首先初始化binder实例 StaticLoggerBinder.getSingleton(); //初始化状态置为成功 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; //报告实际绑定路径 reportActualBinding(staticLoggerBinderPathSet); fixSubstituteLoggers(); replayEvents(); // release all resources in SUBST_FACTORY SUBST_FACTORY.clear(); } 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.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) { 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.LoggerFactory通过StaticLoggerBinder获取ILoggerFactory的实现类。
2.通过ILoggerFactory的实现类获取Logger的实现类返回。