文档章节

slf4J源码初探

筷筷
 筷筷
发布于 2017/07/25 11:54
字数 2039
阅读 16
收藏 0

一、日志实现工具和日志接口简介

java日志工具主要包含两个方面:日志接口工具和日志实现工具

  • 日志实现工具
  1. Apache 开发了功能强大的log4j,并申请纳入jdk

  2. sun不接受apache 的申请,而是在jdk1.4中自己开发了Java.util.logging
  3. logback出自log4j的作者之手,用于替代log4j,Logback的内核重写了log4j,在一些关键执行路径上性能提升10倍以上,而且logback不仅性能提升了,初始化内存加载也更小了。 logback非常自然实现了slf4j, logback-classic实现了slf4j,作为slf4j的实现时无需适配器。
  • 日志接口
  1. slf4j 简单日志门面(Simple Logging Facade for Java)。 slf4j同样是日志接口,使用静态绑定的方式支持log4jjdklogback等作为日志实现,也支持静态绑定common-logging作为中间层,然后通过common-logging的动态加载机制选择日志实现。使用SLF4J时,如果需要使用某一种日志实现,那么必须选择正确的适配器,比如:slf4j-log4j12.jar, slf4j- jdk14.jar等
  2. JCL Java通用日志接口 Apche common-logging。  commons-logging支持log4jjdk Java.util.logging作为其日志实现,使用时通过动态加载的方式寻找。 commons-logging启动后在 classpath 如果能找到log4j,则默认使用log4j 作为日志实现,如果没有则使用JDK14Logger 实现,如果JDK版本低于1.4,则使用其内部提供的 SimpleLog 实现。JCL的缺点:Apache Common-Logging使用了ClassLoader寻找和载入底层的日志库。而OSGI中,不同的插件使用自己的ClassLoader。一个线程的ClassLoader在执行不同的插件时,其执行能力是不同的。OSGI的这种机制保证了插件互相独立,然而确使Apache Common-Logging无法工作。
  3. JCL 只提供 log 接口,具体的实现则在运行时动态寻找。slf4j同样也是日志接口,使用静态绑定的方式支持log4jjdklogback等作为日志实现,也支持静态绑定common-logging作为中间层,然后通过common-logging的动态加载机制选择日志实现。

二、日志接口slf4j源码解析

1、引言

在阅读本小节前,最好先了解下log4j,logback,jdk-log,slf4j的知识,编写一个能够打印日志的demo就可以了。

日志接口不是具体的日志解决方案,它只服务于各种各样的日志系统 。而是一个抽象层( abstraction layer),它允许你在后台使用任意一个日志类库。如果是在编写供内外部都可以使用的API或者通用类库,那么你真不会希望使用你类库的客户端必须使用你选择的日志类库。

log4j,logback,jdk-log都为slfj提供了实现,在引入这个三个工具的slf4j的后

  <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.7</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.16</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.5.6</version>
    </dependency>

这三个jar中都有StaticLoggerBindder这个类。(当依赖的jar包版本出现冲突(groupId和artifactId相同,但version不同)时,优先级规则为:在依赖树中越靠近项目根节点的jar优先级越高,若两个jar在依赖树中所处层次相同则在pom.xml中越早出现的jar优先级越高。

如果两个jar里面的package名和class名都相同,但是groupId或artifactId不同,例如log4j-1.2.17与桥接器log4j-over-slf4j-1.7.21就符合这样的状况,程序加载org.apache.log4j.LogManager类的顺序适用于上述优先级规则。)

logback有些不一样,这点留到我们后面再讲。

至于为什么,我们一步一步看。我们在项目中引入slf4j-api,slf4j-xxx,xxx三个jar包之后,下面代码就可以正确的执行

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    public static Logger logger = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) {
        logger.info("Hello {}!", "world");
    }
}

在上述代码中并没有引入log4j,logback,jdk-log相关的类,而当我们去掉slf4j-xxx这个jar包之后,就会提示以下错误:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

我们可以猜测,在LoggerFactory或者Loger中引用到了org.slf4j.impl.StaticLoggerBinder。

2.获取LoggerFactory

2.1 首先看getLogger(class a),这个方法又调用了getLogger(String name)的方法

 public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

2.2 代码第一行获取了一个iLoggerFactory,继续点进去看

/**
     * Return the {@link ILoggerFactory} instance in use.
     * <p/>
     * <p/>
     * ILoggerFactory instance is bound with this class at compile time.
     * 
     * @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");
    }

if语句代表,如果没有初始化的话,就进入一个同步块执行performInitialization()的方法,如果init成功则返回一个通过StaticLoggerBindder创建的LoggerFactory。(这里就结合前面提到问题考虑,为什么slf4j-jdk,slf4j-log4j,logback-classic中都包含StaticLoggerBindder类,以及为引入这三个jar时,slf4j提示的异常)。

2.3 performInitialization()方法里面调用了bind()方法,然后进行进行版本检查,这里我们重点关注bind()方法。

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
//找到所有的StaticLoggerBinder 
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
/*如果有多个,则提示载入了多个StaticLoggerBinder 实现,可能引入了slf4j-jdk,slf4j-log4j,
logback-classic中的两个或者三个*/
/*如果三个都引入了,会提示:
SLF4J: Found binding in [jar:file:/E:/maven/repository/
ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/maven/repository/
org/slf4j/slf4j-log4j12/1.7.16/slf4j-log4j12-1.7.16.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/maven/repository/
org/slf4j/slf4j-jdk14/1.5.6/slf4j-jdk14-1.5.6.jar!/org/slf4j/impl/StaticLoggerBinder.class]
*/
//提示有多个地方有StaticLoggerBinder
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
//获取StaticLoggerBinder的大力
            StaticLoggerBinder.getSingleton();
//设置状态,初始化成功
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
//report实际的loggerFactory的类,例如 SLF4J: Actual binding is of type 
//[ch.qos.logback.classic.util.ContextSelectorStaticBinder]
            reportActualBinding(staticLoggerBinderPathSet);
//这句应该是处理在初始化过程中阻塞的输入日志事件
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } catch (.....)
           {...  }
    }

代码非常长,不过我们暂时只关心try 块的代码,先理清最主要的脉络。staticLoggerBinderPathSet用来存储 StaticLoggerBindder的路径集合。
如果有多个路径就提示异常。

2.4 然后看找StaticLoggerBindder路径的代码,还是比较好理解的

// We need to use the name of the StaticLoggerBinder class, but we can't reference the class itself.
	private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

	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);
			}
//将path加入集合中
			while (paths.hasMoreElements())
			{
				URL path = paths.nextElement();
				staticLoggerBinderPathSet.add(path);
			}
		}
		catch (IOException ioe)
		{
			Util.report("Error getting resources from path", ioe);
		}
		return staticLoggerBinderPathSet;
	}

STATIC_LOGGER_BINDER_PATH是类的路径。首先获取到LoggerFactory的类加载器。如果找不到,就调用ClassLoader的类方法,查找所有的资源;如果找到了LoggerFactory的类加载器,就根据类加载器找对应的路径。然后将找到的路径存入staticLoggerBinderPathSet集合中返回。

2.5 回到2.2小节的代码,init成功之后直接返回执行下列代码

 return StaticLoggerBinder.getSingleton().getLoggerFactory();

slf4j中是没有StaticLogerBinder,得去slf4j-jdk,slf4j-log4j,logback-classic中看

我们选择log4j,代码比较简单,简单的单例模式。

public class StaticLoggerBinder implements LoggerFactoryBinder {


    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    public static String REQUESTED_API_VERSION = "1.6.99"; // !final

    private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();

    private final ILoggerFactory loggerFactory;

    private StaticLoggerBinder() {
        loggerFactory = new Log4jLoggerFactory();
        try {
            @SuppressWarnings("unused")
            Level level = Level.TRACE;
        } catch (NoSuchFieldError nsfe) {
            Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
        }
    }

    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

2.6 小节

slf4j-jdk,slf4j-log4j都有slf4j的slf4j logger接口的实现分别是 JDK14LoggerAdapter和Log4jLoggerAdapter,而logback本身就实现了slf4j,不需要额外的适配器。

后面该写日志桥接方面的知识

© 著作权归作者所有

共有 人打赏支持
筷筷
粉丝 1
博文 11
码字总数 10477
作品 0
南京
程序员
spring3,unitils 与dbunit整合问题记录

unitils 3.3版本,spring3.2.4 1.Caused by: java.lang.IllegalStateException: org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.htm......

听风雨
2013/10/07
0
0
slf4j与jul、log4j1、log4j2、logback的集成原理

1 系列目录 - jdk-logging、log4j、logback日志介绍及原理- commons-logging与jdk-logging、log4j1、log4j2、logback的集成原理- slf4j与jdk-logging、log4j1、log4j2、logback的集成原理- s...

乒乓狂魔
2015/04/30
0
5
Spring3 MyBatis3 日志配置

//org.apache.ibatis.logging.LogFactory; static { } 查看Mybatis源码发现,他日志框架的寻找顺序:slf4j-->commons logging-->log4j 不能直接引入log4j jar包,因为即使我不导入slf4j的包,...

xingda
2012/04/17
0
0
NoClassDefFoundError: ch/qos/logback/classic/spi/ThrowableProxy

报错日志: 解决方案: 解决冲突。 附: SLF4J warning or error messages and their meanings No SLF4J providers were found. This warning, i.e. not an error, message is reported whe......

程序员诗人
05/17
0
0
slf4j、jcl、jul、log4j1、log4j2、logback大总结

1 系列目录 - jdk-logging、log4j、logback日志介绍及原理- commons-logging与jdk-logging、log4j1、log4j2、logback的集成原理- slf4j与jdk-logging、log4j1、log4j2、logback的集成原理- s...

乒乓狂魔
2015/05/04
0
33

没有更多内容

加载失败,请刷新页面

加载更多

学习设计模式——命令模式

参考博客 1. 认识命令模式 1. 定义:将一个请求封装成为一个对象,从而可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,并支持可撤销操作。 2. 组织结构: Commond:定义命令的...

江左煤郎
16分钟前
0
0
字典树收集(非线程安全,后续做线程安全改进)

将500W个单词放进一个数据结构进行存储,然后进行快速比对,判断一个单词是不是这个500W单词之中的;来了一个单词前缀,给出500w个单词中有多少个单词是该前缀. 1、这个需求首先需要设计好数据结...

算法之名
昨天
6
0
GRASP设计模式

此文参考了这篇博客,建议读者阅读原文。 面向对象(Object-Oriented,OO)是当下软件开发的主流方法。在OO分析与设计中,我们首先从问题领域中抽象出领域模型,在领域模型中以适当的粒度归纳...

克虏伯
昨天
0
0
Coding and Paper Letter(四十)

资源整理。 1 Coding: 1.Tomislav Hengl撰写的非官方作者指南:Michael Gould•Wouter Gerritsma。 UnofficialGuide4Authors 2.R语言包rwrfhydro,社区贡献的工具箱,用于管理,分析和可视化...

胖胖雕
昨天
0
0
JAVA 内存回收

参考:https://www.cnblogs.com/leesf456/p/5218594.html 1,JMV 中哪些可以作为 GC Root? 1. 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。 2. 方法区中的类静态属性引...

Carlyle_Lee
昨天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部