`

【飞天奔月出品】剖析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  
非常清晰的思路,学习了!另外弱弱的问问楼主,博文中的流程图是用什么软件绘制的?感觉好高大上!

相关推荐

    【飞天奔月出品】windows版nginx 快速操控神器(启动start,关闭stop,重启restart) 批处理

    标题中的“【飞天奔月出品】windows版nginx 快速操控神器(启动start,关闭stop,重启restart) 批处理”指的是一个专为Windows操作系统设计的Nginx管理工具,它通过批处理脚本实现了Nginx服务的便捷启动、停止和重启...

    奔月生物:2021年半年度报告.PDF

    奔月生物:2021年半年度报告.PDF

    奔月生物:2021年半年度报告.rar

    2. **经营业绩**:详细展示2021年上半年的销售收入、净利润、毛利润等关键财务指标,对比去年同期的数据,分析增长或下滑的原因。 3. **财务分析**:深入解析公司的资产负债表、利润表和现金流量表,评估公司的财务...

    鲁迅《奔月》作品分析.pdf

    从文化视角分析,《奔月》揭示了中国传统文化中的某些消极因素。鲁迅将国民性概括为“愚弱”,表现为缺乏是非观念和不敢抗争的懦弱。嫦娥的冷漠、老太太的愚昧、逢蒙的狡诈,都在一定程度上映射了这种国民性。鲁迅借...

    奔月生物:2019年半年度报告.PDF

    2. **公司更名与地址变更**:2019年1月16日,公司名称由“山东奔月生物科技有限公司”变更为“山东奔月生物科技股份有限公司”,同时生产地址也进行了更新,从“山东东营利津刁口政府驻地”迁移到“山东省东营市利津...

    HTML5奔月游戏

    HTML5奔月游戏是一款完全基于HTML5技术开发的互动小游戏,玩家可以扮演一只兔子,目标是在游戏中尽可能地收集月饼并安全抵达月球。这款游戏的亮点在于其无需任何额外的插件或软件支持,只需浏览器就能运行,这得益于...

    火箭兔奔月HTML5游戏源码

    在火箭兔奔月这个游戏源码中,我们可以期待看到以下关键技术和概念: 1. **Canvas绘图**:游戏的主要画面很可能是通过`canvas`元素进行绘制的,包括火箭兔的动画、月球背景和其他游戏元素。开发者会利用JavaScript...

    H5游戏源码 奔月游戏.zip

    《H5游戏源码解析:奔月游戏》 在当今数字化时代,HTML5(简称H5)技术以其跨平台、轻量级、易部署的特点,成为制作网页游戏的热门选择。"奔月游戏"作为一款H5游戏,其源码为我们提供了一窥H5游戏开发的窗口。本文...

    手绘实例:奔月 Photoshop3 图片处理 高级手绘技术技巧.doc

    手绘实例:奔月 Photoshop3 图片处理 高级手绘技术技巧

    HTML5奔月游戏 源码.zip

    在奔月游戏中,可能使用canvas来绘制角色、背景、轨迹等元素,并通过定时更新画面来实现动画效果。 2. **Web Audio API**:HTML5提供了Web Audio API,用于处理和播放音频。游戏往往需要背景音乐和音效,这个API...

    Photoshop合成奔月女孩梦幻艺术照片效果.doc

    - 打开素材2,使用你喜欢的方法(例如钢笔工具或通道)将女孩抠出并拖入"奔月女孩"文件中,新图层命名为"女孩"。 - 使用自由变换(Ctrl+T)调整女孩的大小和位置。 3. **色彩平衡调整**: - 选择"女孩"图层,通过...

    小游戏源码-火贱兔奔月.rar

    【标题】"小游戏源码-火贱兔奔月.rar" 提供的是一个小型游戏的源代码,名为"火贱兔奔月"。这类源码通常用于教学、学习或游戏开发者的参考,帮助开发者理解游戏的基本架构和编程逻辑。 【描述】"小游戏源码-火贱兔...

    长娥奔月模板下载TIF

    【长娥奔月模板下载TIF】是一个与网页设计相关的资源,主要提供了一种以"长娥奔月"为主题的网页模版。这个模版可能是为了庆祝中国传统节日,如中秋节,或者用于讲述中国古老的神话故事。"长娥奔月"是中国文化中的...

    析鲁迅《奔月》.docx

    《鲁迅的《奔月》:颠覆传统,开创审美新向度》 鲁迅的短篇小说《奔月》是其《故事新编》中的一篇,它颠覆了我们对古代神话的传统认知,尤其对嫦娥这一角色的刻画,使得这篇作品在文学史上占据了独特的地位。鲁迅...

    HTML5小游戏【火贱兔奔月-425款经典优秀H5小游戏合集】游戏源码分享下载 - hjby.zip

    游戏源码分享下载 --- hjby.zipHTML5小游戏【火贱兔奔月--425款经典优秀H5小游戏合集】游戏源码分享下载 --- hjby.zipHTML5小游戏【火贱兔奔月--425款经典优秀H5小游戏合集】游戏源码分享下载 --- hjby.zipHTML5小...

    HTML5兔子奔月吃月饼游戏源码

    2. **JavaScript编程**: 游戏逻辑和交互功能通常由JavaScript控制。JavaScript用于响应用户的输入(如点击或触摸),更新游戏状态,处理碰撞检测,计算分数,以及控制游戏流程。此外,JavaScript还可以实现计时器...

    [鲁迅]析鲁迅《奔月》.docx

    综上所述,鲁迅的《奔月》不仅是对传统神话的颠覆,更是对现实社会的深刻剖析。通过将传统神圣凡俗化,鲁迅开辟了新的审美向度,体现了他的先锋精神。他的作品不仅是文学艺术的创新,也是对时代问题的敏锐洞察和批判...

    HTML5实现兔子中秋奔月吃月饼游戏源码.zip

    在提供的文件列表中,“使用须知.txt”很可能是包含了游戏运行的说明和注意事项,比如如何启动游戏、如何操作兔子移动等。而“132692081374528955”可能是一个压缩的文件或者数据,可能包含了游戏的主要代码、图像...

    H5小游戏源码 火贱兔奔月.zip

    2. JavaScript文件:主要包含游戏的逻辑代码,包括初始化、事件处理、游戏循环、角色行为、分数计算等核心功能。 3. CSS文件:定义游戏界面的样式,包括背景、按钮、角色样式等,可能包含关键帧动画来实现动态效果。...

    初中语文文摘生活且看嫦娥咋奔月

    初中语文文摘生活且看嫦娥咋奔月

Global site tag (gtag.js) - Google Analytics