一、整体介绍

介绍:

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

工作模式:

log4j写入kafka log4j slf4j logback_加载

sl4j-api结构图

log4j写入kafka log4j slf4j logback_log4j写入kafka_02

根据入口代码来分析。

Logger logger = LoggerFactory.getLogger(logbackTest.class);

我们可以知道初始化,是从这个方法开始的。

首先入口代码非常简单

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
  } 
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");
  }

  

上面基本上就是整个调用流程的代码了。第5行通过factory模式得到了一个日志工厂,第6行根据名字获取日志logger。

第3个函数是主要的逻辑函数,根据INITIALIZATION_STATE 不同状态,做不同的处理。

当INITIALIZATION_STATE ==0时,初始化日志框架【9行】

当INITIALIZATION_STATE ==1时,返回的TEMP_FACTORY,我们尅看下定义【15行】

static SubstituteLoggerFactory TEMP_FACTORY = new SubstituteLoggerFactory();

SubstituteLoggerFactory是什么尼,很简单的结构,是由sl4j实现的一个。。,看一下文档描述:

SubstituteLoggerFactory 简单实现了ILoggerFactory接口,并总是返回一个单例的 NOPLogger实例。

作用:

* It used as a temporary substitute for the real ILoggerFactory during its
* auto-configuration which may re-enter LoggerFactory to obtain logger
* instances. See also http://bugzilla.slf4j.org/show_bug.cgi?id=106

log4j写入kafka log4j slf4j logback_log4j写入kafka_03

当INITIALIZATION_STATE ==2时,返回的失败信息【17行】,其中我们可以看下各种状态枚举定义,可以知道2对应的状态是日志初始化失败

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;

当INITIALIZATION_STATE ==3时,返回的失败信息【19行】,显然是日志初始化成功,获取日志单例信息。

当INITIALIZATION_STATE ==4时,返回的失败信息【21行】,我们看见返回会的是NOP_FALLBACK_FACTORY(没有找到桥接jar)对象,看下定义

static NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory();
/**
 * NOPLoggerFactory is an trivial implementation of {@link
 * ILoggerFactory} which always returns the unique instance of
 * NOPLogger.
 * 
 * @author Ceki Gülcü
 */
public class NOPLoggerFactory implements ILoggerFactory {
  
  public NOPLoggerFactory() {
    // nothing to do
  }
  
  public Logger getLogger(String name) {
    return NOPLogger.NOP_LOGGER;
  }

}

根据定义和和注释,我们我们可以大胆猜测,4状态就是什么都不做,NO Operator Logger Factory.仅仅是一个空实现。

官网描述:SINCE 1.6.0 If no binding is found on the class path, then SLF4J will default to a no-operation implementation.

二、核心代码分析

重点分析这个方法performInitialization();
1   private final static void performInitialization() {
 2     bind();
 3     if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
 4       versionSanityCheck();
 5     }
 6   }    
 7 private final static void bind() {
 8     try {
 9       Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
10       reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
11       // the next line does the binding
12       StaticLoggerBinder.getSingleton();
13       INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
14       reportActualBinding(staticLoggerBinderPathSet);
15       emitSubstituteLoggerWarning();
16     } catch (NoClassDefFoundError ncde) {
17       String msg = ncde.getMessage();
18       if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
19         INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
20         Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
21         Util.report("Defaulting to no-operation (NOP) logger implementation");
22         Util.report("See " + NO_STATICLOGGERBINDER_URL
23                 + " for further details.");
24       } else {
25         failedBinding(ncde);
26         throw ncde;
27       }
28     } catch (java.lang.NoSuchMethodError nsme) {
29       String msg = nsme.getMessage();
30       if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
31         INITIALIZATION_STATE = FAILED_INITIALIZATION;
32         Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
33         Util.report("Your binding is version 1.5.5 or earlier.");
34         Util.report("Upgrade your binding to version 1.6.x.");
35       }
36       throw nsme;
37     } catch (Exception e) {
38       failedBinding(e);
39       throw new IllegalStateException("Unexpected initialization failure", e);
40     }
41   }

首先进入bind方法,

bind方法第9行,findPossibleStaticLoggerBinderPathSet(),根据名字我们知道这个函数的功能是查找可能的StaticLoggerBinder路径集合。

1   private static Set findPossibleStaticLoggerBinderPathSet() {
 2     // use Set instead of list in order to deal with  bug #138
 3     // LinkedHashSet appropriate here because it preserves insertion order during iteration
 4     Set staticLoggerBinderPathSet = new LinkedHashSet();
 5     try {
 6       ClassLoader loggerFactoryClassLoader = LoggerFactory.class
 7               .getClassLoader();
 8       Enumeration paths;
 9       if (loggerFactoryClassLoader == null) {
10         paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
11       } else {
12         paths = loggerFactoryClassLoader
13                 .getResources(STATIC_LOGGER_BINDER_PATH);
14       }
15       while (paths.hasMoreElements()) {
16         URL path = (URL) paths.nextElement();
17         staticLoggerBinderPathSet.add(path);
18       }
19     } catch (IOException ioe) {
20       Util.report("Error getting resources from path", ioe);
21     }
22     return staticLoggerBinderPathSet;
23   }

我们可以看到都是在加载StaticLoggerBinder.class类

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

那么StaticLoggerBinder.class是干嘛的尼?传送门:http://skyao.github.io/2014/07/21/slfj4-binding/,后续会对StaticLoggerBinder仔细分析。

bind方法第10行,假设找到多个StaticLoggerBinder.class,就提示输出信息。

bind方法第12行,很关键的一行代码,和第10行关联起来了。获取一个StaticLoggerBinder单例.

1  private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
 2  public static StaticLoggerBinder getSingleton() {
 3     return SINGLETON;
 4   }
 5   static {
 6     SINGLETON.init();
 7   }
 8   void init() {
 9     try {
10       try {
11         new ContextInitializer(defaultLoggerContext).autoConfig();
12       } catch (JoranException je) {
13         Util.report("Failed to auto configure default logger context", je);
14       }
15       // logback-292
16       if(!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
17         StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
18       }
19       contextSelectorBinder.init(defaultLoggerContext, KEY);
20       initialized = true;
21     } catch (Throwable t) {
22       // we should never get here
23       Util.report("Failed to instantiate [" + LoggerContext.class.getName()
24           + "]", t);
25     }
26   }

可以看出其实就是一个单例,并初始化了。初始化的具体详情,我们在后续会分析。这里我们只需要知道得到了一个StaticLoggerBinder单例对象即可。

bind方法14,15,16行主要是改变状态,显示警告信息等。

由此我们知道bind方法核心功能是加载了StaticLoggerBinder.class,并初始化了单例对象。

我们回到上面getILoggerFactory方法。

getILoggerFactory方法19行代码:StaticLoggerBinder.getSingleton().getLoggerFactory();

即StaticLoggerBinder的getLoggerFactory方法,我们看一下定义。

1 public ILoggerFactory getLoggerFactory() {
 2     if (!initialized) {
 3       return defaultLoggerContext;
 4     }
 5 
 6     if (contextSelectorBinder.getContextSelector() == null) {
 7       throw new IllegalStateException(
 8           "contextSelector cannot be null. See also " + NULL_CS_URL);
 9     }
10     return contextSelectorBinder.getContextSelector().getLoggerContext();
11   }

在第二行判断initialized状态在init()方法第20行知道,在初始化完成后,即为true。所以getLoggerFactory方法会直接到第6行。

private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
public static ContextSelectorStaticBinder getSingleton() {
    return singleton;
}
static ContextSelectorStaticBinder singleton = new ContextSelectorStaticBinder();

第6行获取到一个ContextSelectorStaticBinder的实例。并且获取getContextSelector方法

1   public ContextSelector getContextSelector() {
 2     return contextSelector;
 3   }
 4  ContextSelector contextSelector;
 5 public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException,
 6       NoSuchMethodException, InstantiationException, IllegalAccessException,
 7       InvocationTargetException  {
 8     if(this.key == null) {
 9       this.key = key;
10     } else if (this.key != key) {
11       throw new IllegalAccessException("Only certain classes can access this method.");
12     }
13     
14     
15     String contextSelectorStr = OptionHelper
16         .getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR);
17     if (contextSelectorStr == null) {
18       contextSelector = new DefaultContextSelector(defaultLoggerContext);
19     } else if (contextSelectorStr.equals("JNDI")) {
20       // if jndi is specified, let's use the appropriate class
21       contextSelector = new ContextJNDISelector(defaultLoggerContext);
22     } else {
23       contextSelector = dynamicalContextSelector(defaultLoggerContext,
24           contextSelectorStr);
25     }
26   }

我们查询代码知道getContextSelector获取的对象是在init()中方法赋值的。

在StaticLoggerBinder类中init()方法第19行代码,进行了ContextSelectorStaticBinder的实例化。

contextSelectorBinder.getContextSelector()根据代码知道,其实就是LoggerContext包装对象。

第10代码,contextSelectorBinder.getContextSelector().getLoggerContext();得到一个LoggerContext对象。

即,我们的getILoggerFactory的方法,得到了一个LoggerContext对象。

在getLogger方法第二行代码,传递name获取Logger对象。

1  public final Logger getLogger(final String name) {
 2 
 3     if (name == null) {
 4       throw new IllegalArgumentException("name argument cannot be null");
 5     }
 6 
 7     // if we are asking for the root logger, then let us return it without
 8     // wasting time
 9     if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
10       return root;
11     }
12 
13     int i = 0;
14     Logger logger = root;
15 
16     // check if the desired logger exists, if it does, return it
17     // without further ado.
18     Logger childLogger = (Logger) loggerCache.get(name);
19     // if we have the child, then let us return it without wasting time
20     if (childLogger != null) {
21       return childLogger;
22     }
23 
24     // if the desired logger does not exist, them create all the loggers
25     // in between as well (if they don't already exist)
26     String childName;
27     while (true) {
28       int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
29       if (h == -1) {
30         childName = name;
31       } else {
32         childName = name.substring(0, h);
33       }
34       // move i left of the last point
35       i = h + 1;
36       synchronized (logger) {
37         childLogger = logger.getChildByName(childName);
38         if (childLogger == null) {
39           childLogger = logger.createChildByName(childName);
40           loggerCache.put(childName, childLogger);
41           incSize();
42         }
43       }
44       logger = childLogger;
45       if (h == -1) {
46         return childLogger;
47       }
48     }
49   }

至此,整个流程就走完了。

下一章,我们会对整个sl4j架构进行分析。分析代码的优缺点。

-------------------------------------------------------------------------华丽分割线,下面是StaticLoggerBinder代码分析-------------------------------

 

 

 

 

 

 

 

在我们的源码目录中,并没有这个类

log4j写入kafka log4j slf4j logback_java_04

那么他在哪里尼?这就是实现接口反转和适配很重要的一个环节了,还记得我们开始第一幅图么

log4j写入kafka log4j slf4j logback_加载_05


sl4j-api会调用

log4j写入kafka log4j slf4j logback_初始化_06

看下类类定StaticLoggerBinder.class:binding一个实际的ILoggerFactory的实例。

* The binding of {@link LoggerFactory} class with an actual instance of
* {@link ILoggerFactory} is performed using information returned by this class.
来吧,一言不合就看源码:
1 public class StaticLoggerBinder implements LoggerFactoryBinder {
 2 
 3   /**
 4    * Declare the version of the SLF4J API this implementation is compiled
 5    * against. The value of this field is usually modified with each release.
 6    */
 7   // to avoid constant folding by the compiler, this field must *not* be final
 8   public static String REQUESTED_API_VERSION = "1.6"; // !final
 9 
10   final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS";
11 
12   /**
13    * The unique instance of this class.
14    */
15   private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
16 
17   private static Object KEY = new Object();
18 
19   static {
20     SINGLETON.init();
21   }
22 
23   private boolean initialized = false;
24   private LoggerContext defaultLoggerContext = new LoggerContext();
25   private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder
26       .getSingleton();
27 
28   private StaticLoggerBinder() {
29     defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
30   }
31 
32   public static StaticLoggerBinder getSingleton() {
33     return SINGLETON;
34   }
35 
36   /**
37    * Package access for testing purposes.
38    */
39   static void reset() {
40     SINGLETON = new StaticLoggerBinder();
41     SINGLETON.init();
42   }
43 
44   /**
45    * Package access for testing purposes.
46    */
47   void init() {
48     try {
49       try {
50         new ContextInitializer(defaultLoggerContext).autoConfig();
51       } catch (JoranException je) {
52         Util.report("Failed to auto configure default logger context", je);
53       }
54       // logback-292
55       if(!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
56         StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
57       }
58       contextSelectorBinder.init(defaultLoggerContext, KEY);
59       initialized = true;
60     } catch (Throwable t) {
61       // we should never get here
62       Util.report("Failed to instantiate [" + LoggerContext.class.getName()
63           + "]", t);
64     }
65   }
66 
67   public ILoggerFactory getLoggerFactory() {
68     if (!initialized) {
69       return defaultLoggerContext;
70     }
71 
72     if (contextSelectorBinder.getContextSelector() == null) {
73       throw new IllegalStateException(
74           "contextSelector cannot be null. See also " + NULL_CS_URL);
75     }
76     return contextSelectorBinder.getContextSelector().getLoggerContext();
77   }
78 
79   public String getLoggerFactoryClassStr() {
80     return contextSelectorBinder.getClass().getName();
81   }
82 
83 }

先看下类图:

log4j写入kafka log4j slf4j logback_java_07