slf4j 之logback日志之sl4j架构【二】

时间:2022-07-09 23:20:32

一、整体介绍

介绍:

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.

工作模式:

slf4j 之logback日志之sl4j架构【二】

sl4j-api结构图

slf4j 之logback日志之sl4j架构【二】

根据入口代码来分析。

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

slf4j 之logback日志之sl4j架构【二】

当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();
   private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
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()") != -) {
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);
}
}

首先进入bind方法,

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

   private static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class
.getClassLoader();
Enumeration 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;
}

我们可以看到都是在加载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单例.

  private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
static {
SINGLETON.init();
}
void init() {
try {
try {
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if(!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Throwable t) {
// we should never get here
Util.report("Failed to instantiate [" + LoggerContext.class.getName()
+ "]", t);
}
}

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

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

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

我们回到上面getILoggerFactory方法。

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

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

 public ILoggerFactory getLoggerFactory() {
if (!initialized) {
return defaultLoggerContext;
} if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException(
"contextSelector cannot be null. See also " + NULL_CS_URL);
}
return contextSelectorBinder.getContextSelector().getLoggerContext();
}

在第二行判断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方法

   public ContextSelector getContextSelector() {
return contextSelector;
}
ContextSelector contextSelector;
public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException,
NoSuchMethodException, InstantiationException, IllegalAccessException,
InvocationTargetException {
if(this.key == null) {
this.key = key;
} else if (this.key != key) {
throw new IllegalAccessException("Only certain classes can access this method.");
} String contextSelectorStr = OptionHelper
.getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR);
if (contextSelectorStr == null) {
contextSelector = new DefaultContextSelector(defaultLoggerContext);
} else if (contextSelectorStr.equals("JNDI")) {
// if jndi is specified, let's use the appropriate class
contextSelector = new ContextJNDISelector(defaultLoggerContext);
} else {
contextSelector = dynamicalContextSelector(defaultLoggerContext,
contextSelectorStr);
}
}

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

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

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

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

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

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

  public final Logger getLogger(final String name) {

     if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
} // if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
} int i = ;
Logger logger = root; // check if the desired logger exists, if it does, return it
// without further ado.
Logger childLogger = (Logger) loggerCache.get(name);
// if we have the child, then let us return it without wasting time
if (childLogger != null) {
return childLogger;
} // if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -) {
childName = name;
} else {
childName = name.substring(, h);
}
// move i left of the last point
i = h + ;
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -) {
return childLogger;
}
}
}

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

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

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

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

slf4j 之logback日志之sl4j架构【二】

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

slf4j 之logback日志之sl4j架构【二】

sl4j-api会调用

slf4j 之logback日志之sl4j架构【二】

看下类类定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.
来吧,一言不合就看源码:
 public class StaticLoggerBinder implements LoggerFactoryBinder {

   /**
* Declare the version of the SLF4J API this implementation is compiled
* against. The value of this field is usually modified with each release.
*/
// to avoid constant folding by the compiler, this field must *not* be final
public static String REQUESTED_API_VERSION = "1.6"; // !final final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS"; /**
* The unique instance of this class.
*/
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); private static Object KEY = new Object(); static {
SINGLETON.init();
} private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder
.getSingleton(); private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
} public static StaticLoggerBinder getSingleton() {
return SINGLETON;
} /**
* Package access for testing purposes.
*/
static void reset() {
SINGLETON = new StaticLoggerBinder();
SINGLETON.init();
} /**
* Package access for testing purposes.
*/
void init() {
try {
try {
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if(!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Throwable t) {
// we should never get here
Util.report("Failed to instantiate [" + LoggerContext.class.getName()
+ "]", t);
}
} public ILoggerFactory getLoggerFactory() {
if (!initialized) {
return defaultLoggerContext;
} if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException(
"contextSelector cannot be null. See also " + NULL_CS_URL);
}
return contextSelectorBinder.getContextSelector().getLoggerContext();
} public String getLoggerFactoryClassStr() {
return contextSelectorBinder.getClass().getName();
} }

先看下类图:

slf4j 之logback日志之sl4j架构【二】