文档章节

slf4J源码初探

筷筷
 筷筷
发布于 2017/07/25 11:54
字数 2039
阅读 12
收藏 0
点赞 0
评论 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
博文 2
码字总数 10477
作品 0
南京
程序员
SLF4J: Multiple bindings were found on the class path

众所周知,SLF4J是一个日志门面框架,它的作用是用于定义统一的日志接口,而具体的日志实现是由各个日志框架实现的,比如log4j,logback等。 问题 在使用SLF4J时,当class path同时包含了多个...

勇敢的飞石 ⋅ 05/17 ⋅ 0

Java混乱的日志体系(logback)(转)

作为一名 Java 程序员,日常开发工作中肯定会接触日志系统,但是众多的框架,包括 Log4j、Log4j2、Logback、Slf4j、Apache Common logging 等等,引用的 maven 依赖众多,到底可以去掉哪些,...

easonjim ⋅ 2017/12/27 ⋅ 0

slf4j 是怎么绑定具体的日志框架的

SLF4J 英文全称是 Simple Logging Facade for Java, 是一个门面(外观)接口或者说各种日志框架的抽象,比如 java.util.logging, logback, log4j 等;使用这货,我们可以不用关心具体的实现...

holysu ⋅ 05/13 ⋅ 0

ssm项目中添加日志 log4j

添加 log4j 环境 1. 导入环境 pom.xml <!-- 12.log4j 下面三个--> <dependency> <groupId>org.bgee.log4jdbc-log4j2</groupId> <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId> <version>......

Lucky_Me ⋅ 06/06 ⋅ 0

Log4j 2.0在开发中的高级使用详解

log4j与slf4j、logback比较 而log4j slf4j logback就是目前主流的日志框架。但后两者效率高是第一个。推荐使用:slf4j 或者 logback(spring-boot默认日志实现) log4j是apache实现的一个开源日...

spinachgit ⋅ 04/22 ⋅ 0

如何在ABAP Netweaver和CloudFoundry里记录并查看日志

Netweaver 要记录日志需要有一个checkpoint group,可以自行创建也可以使用标准的。这里我重用标准的group:DEMOCHECKPOINTGROUP。 tcode SAAB,点Display <->Activate进入编辑模式,将Logpo...

JerryWang_SAP ⋅ 06/10 ⋅ 0

Java日志框架-logback的介绍及配置使用方法(纯Java工程)(转)

说明:内容估计有些旧,2011年的,但是大体意思应该没多大变化,最新的配置可以参考官方文档。 一、logback的介绍 Logback是由log4j创始人设计的又一个开源日志组件。logback当前分成三个模块...

easonjim ⋅ 2017/11/07 ⋅ 0

每个项目中,你必须知道的11个Java第三方类库。

Java第三方library ecosystem是一个很广阔的范畴。不久前有人撰文:每个项目中,你必须知道的11个Java第三方类库。 单元测试 1.DBUnit DBunit是一个基于junit扩展的数据库测试框架。它提供了...

thinkyoung ⋅ 2015/01/07 ⋅ 0

分布式链路追踪中间件 - SOFATracer

SOFATracer 是一个用于分布式系统调用跟踪的组件,通过统一的 将调用链路中的各种网络调用情况以日志的方式记录下来,以达到透视化网络调用的目的。这些日志可用于故障的快速发现,服务治理等...

匿名 ⋅ 05/31 ⋅ 0

Spring Boot中Starter是什么

比如我们要在Spring Boot中引入Web MVC的支持时,我们通常会引入这个模块spring-boot-starter-web,而这个模块如果解压包出来会发现里面什么都没有,只定义了一些POM依赖。 经过研究,Start...

easonjim ⋅ 2017/09/30 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JavaScript零基础入门——(十)JavaScript的DOM基础

JavaScript零基础入门——(十)JavaScript的DOM基础 欢迎大家回到我们的JavaScript零基础入门,上一节课,我们了解了JavaScript中的函数,这一节课,我们来了解一下JavaScript的DOM。 第一节...

JandenMa ⋅ 45分钟前 ⋅ 0

Spring mvc DispatchServlet 实现原理

在Spring中, ContextLoaderListener只是辅助类,在web 容器启动的时候查找并创建WebApplicationContext对象,通过该对象进行加载spring的配置文件。而真正的逻辑实现其实是在DispatcherSer...

轨迹_ ⋅ 57分钟前 ⋅ 0

Weex起步

本教程假设你已经在你的本地环境安装了node 其实weex起步教程在 https://github.com/lilugirl/incubator-weex 项目说明文件中都已经有了,但为了有些同学看到英文秒变文盲,所以这里我重新写...

lilugirl ⋅ 今天 ⋅ 0

Jenkins实践1 之安装

1 下载 http://mirrors.jenkins.io/war/latest/jenkins.war 2 启动 java -jar jenkins.war 前提:安装jdk并配置环境变量 启动结果节选: ************************************************......

晨猫 ⋅ 今天 ⋅ 0

组合数学 1-2000 中,能被6或10整除的数的个数

1--2000 中,能被6或10整除的数的个数 利用集合的性质 能被6整除的个数 2000/6 = 333 能被10整除的个数 2000/10 = 200 能被6和10整除的个数 2000/30 = 66 能被6或10整除的个数 333+200-66 =...

阿豪boy ⋅ 今天 ⋅ 0

一篇文章学懂Shell脚本

Shell脚本,就是利用Shell的命令解释的功能,对一个纯文本的文件进行解析,然后执行这些功能,也可以说Shell脚本就是一系列命令的集合。 Shell可以直接使用在win/Unix/Linux上面,并且可以调用...

Jake_xun ⋅ 今天 ⋅ 0

大数据工程师需要精通算法吗,要达到一个什么程度呢?

机器学习是人工智能的一个重要分支,而机器学习下最重要的就是算法,本文讲述归纳了入门级的几个机器学习算法,加大数据学习群:716581014一起加入AI技术大本营。 1、监督学习算法 这个算法由...

董黎明 ⋅ 今天 ⋅ 0

Kylin 对维度表的的要求

1.要具有数据一致性,主键值必须是唯一的;Kylin 会进行检查,如果有两行的主键值相同则会报错。 2.维度表越小越好,因为 Kylin 会将维度表加载到内存中供查询;过大的表不适合作为维度表,默...

无精疯 ⋅ 今天 ⋅ 0

58到家数据库30条军规解读

军规适用场景:并发量大、数据量大的互联网业务 军规:介绍内容 解读:讲解原因,解读比军规更重要 一、基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务、行级锁、并发性能更好、CPU及...

kim_o ⋅ 今天 ⋅ 0

代码注释中顺序更改 文件读写换行

`package ssh; import com.xxx.common.log.LogFactory; import com.xxx.common.log.LoggerUtil; import org.apache.commons.lang3.StringUtils; import java.io.*; public class DirErgodic ......

林伟琨 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部