`

【飞天奔月出品】剖析logback2:logback启动

    博客分类:
  • log
阅读更多

1.logback这货是怎么启动呢?

 

就系统启动的时候,会自动读取配置文件,以便后续代码执行这操行,那必定是在 static block中执行的 

 

 static{

        do logic;

}

 

并且这个类要被加载,static block也好,static变量也好 才会执行

 

关于static block 参见我曾在百度知道的一个回帖

http://zhidao.baidu.com/question/294516388

 

顺着藤,摸着瓜,果不其然 logback-classic-1.1.13.jar中的 org.slf4j.impl.StaticLoggerBinder类 

 

 

  /**
   * The unique instance of this class.
   */
  private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

  private static Object KEY = new Object();

  static {
    SINGLETON.init();
  }

 

 

我们来看看 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);
    }
  }

 

 

跟踪代码, 发现

 

step1. 加载配置文件

关于加载配置文件的顺序, 可以参看 我的这个帖子 logback1.1.13配置文件加载顺序  

 

step2. 构造 contextSelector

 

我们来看看   ch.qos.logback.classic.util.ContextSelectorStaticBinder.init(LoggerContext, Object) 代码

 

  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);
    }
  }

 

转换成流程图是 :

 




 

2.好,那么问题来了,代码中使用 log.debug(叉叉叉),log.info(叉叉叉),log.warn(叉叉叉),log.info(叉叉叉)等等会发生什么事情呢? 为毛这里的log 是slf4j的log 会调用到logback的配置呢?

 

好,带着问题,我们再来走两步 

 

瞧瞧这个简单的  HelloWorldTest 测试类 

 

public class HelloWorldTest{

    /** The Constant log. */
    private static final Logger log = LoggerFactory.getLogger(HelloWorldTest.class);

    @Test
    public void testHelloWorldTest(){
        log.error("hello world");
    }

}

 

我们上面的问题,其实就发生在   LoggerFactory.getLogger(HelloWorldTest.class)

 

通过跟踪代码,我们发现 初始化 perform Initialization会调用

org.slf4j.LoggerFactory.findPossibleStaticLoggerBinderPathSet() 来查找项目中的 "org/slf4j/impl/StaticLoggerBinder.class" 类

 

private static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order during iteration
        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;
    }

 

当然咯,从代码中可见, 如果项目中 有多个  org/slf4j/impl/StaticLoggerBinder.class  类 会提示 "Class path contains multiple SLF4J bindings."

 

    private static void reportMultipleBindingAmbiguity(Set<URL> staticLoggerBinderPathSet) {
        if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
            Util.report("Class path contains multiple SLF4J bindings.");
            Iterator<URL> iterator = staticLoggerBinderPathSet.iterator();
            while (iterator.hasNext()) {
                URL path = (URL) iterator.next();
                Util.report("Found binding in [" + path + "]");
            }
            Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
        }
    }

 

而  org.slf4j.LoggerFactory.bind()方法中的 StaticLoggerBinder.getSingleton();

首先会加载StaticLoggerBinder类,从而调用本文开头写的static block方法,从而调用 init方法

 

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstitutedLoggers();

 

而这些操作都执行完了,会调用  StaticLoggerBinder.getSingleton().getLoggerFactory() 方法返回

 

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");
    }

 

我们再来看看 org.slf4j.impl.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();
  }

 

好,这么一转,又回到了我们之前写的 contextSeletor部分了 

 

 

从而基本上,我们可以确定,

一般情况下, 会使用ch.qos.logback.classic.selector.DefaultContextSelector,使用的是 默认的  ch.qos.logback.classic.LoggerContext

 

3.如果重复调用 Logger log = LoggerFactory.getLogger(HelloWorldTest.class) 会怎么样呢?

 

改造下代码 

 

public class HelloWorldTest{

    /**
     * TestHelloWorldTest.
     * 
     * @throws InterruptedException
     */
    @Test
    public void testHelloWorldTest() throws InterruptedException{
        Logger log = LoggerFactory.getLogger(HelloWorldTest.class);
        log.error("hello world");
    }
}

 

从代码中发现, 如果第二次调用 

 

  public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            INITIALIZATION_STATE = ONGOING_INITIALIZATION;
            performInitialization();
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();

 

此处直接返回 StaticLoggerBinder.getSingleton().getLoggerFactory();

 

由于StaticLoggerBinder 是单例模式的设计,此处不会重复初始化log context

 

而从LoggerFactory 到log 的过程中,我们发现 log 有 map(ConcurrentHashMap) cache 机制,第一次会通过logger name得到log 会设置到map cache中,第二次,相同的logger name 会直接从map cache 中返回

 

 

 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 = 0;
    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;
    }

 

4.好,让我们转成流程图 

 




 
 

 

 

  • 大小: 18 KB
  • 大小: 44.7 KB
3
2
分享到:
评论
2 楼 飞天奔月 2015-04-26  
king_hacker 写道
非常清晰的思路,学习了!另外弱弱的问问楼主,博文中的流程图是用什么软件绘制的?感觉好高大上!


谢谢参与

软件是 visio
1 楼 king_hacker 2015-04-24  
非常清晰的思路,学习了!另外弱弱的问问楼主,博文中的流程图是用什么软件绘制的?感觉好高大上!

相关推荐

Global site tag (gtag.js) - Google Analytics