Ⅱ又贝人韦 Ⅱ又贝人韦
首页
JAVA
Golang
  • Spring
  • 日志框架
  • MySQL
  • 全链路跟踪
  • 资源
  • MAC使用技巧
GitHub (opens new window)
首页
JAVA
Golang
  • Spring
  • 日志框架
  • MySQL
  • 全链路跟踪
  • 资源
  • MAC使用技巧
GitHub (opens new window)
  • Spring
  • 日志框架

    • JAVA日志框架介绍
    • LOG4J使用技巧
    • SLF4J和Java SPI
      • SL4J介绍
      • SLF4J实现方式
        • SLF4J 1.8之前实现方式
        • SLF4J 1.8之后实现方式
      • Java SPI使用案例
        • SPI机制的约定
        • 使用案例
      • 参考
  • 开源框架
  • 日志框架
shixw
2021-10-08
目录

SLF4J和Java SPI

# SL4J介绍

在各种Java开发规范中,针对日志打印,都要求使用SLF4J api,而不是要直接使用日志框架。 那么为什么要使用SLF4J,SLF4J的作用到底是什么,以下引用官网的一段介绍:

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是一个“日志门面”(Logging Facade)它允许用户自由的切换所需的日志框架。

其实SLF4J是典型的面向接口的编程,只是一个日志标准,只提供了一套记录日志的api,并没有日志系统的具体实现,而是通过对接如log4j、java.util.logging等日志框架,来实现日志的输出,各个应用程序在使用日志的的地方全部使用slf4j-api 提供的接口进行编程,系统中具体使用哪个日志框架觉得于引入哪个具体的实现。

以下是SLF4J官网的一张图,揭示了SLF4J和现有主流日志框架的集成方式:

上图中所有的和日志框架的集成都是由SLF4J团队开发的,但是和log4j集成的是log4j1,如果使用log4j2需要使用 log4j-slf4j-impl ,此包为log4j团队实现,具体依赖如下:

 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-slf4j-impl</artifactId>
     <version>xxx.xx.xx</version>
 </dependency>
1
2
3
4
5

# SLF4J实现方式

日志框架有很多,他们是如何被slf4j发现并绑定的?接下来就从源码层面来看一下SLF4J的实现方式,注: 由于SLF4J 1.8 版本以后修改为使用ServiceLoader来加载具体日志实现,所以此处会分为两部分来介绍。

# SLF4J 1.8之前实现方式

注: 代码基于 1.7.30 版本

在使用SLF4J开发时我们使用以下代码,可以看出主要涉及两个类:org.slf4j.Logger 和 org.slf4j.LoggerFactory ,通过源码可以看出Logger是一个接口,定义来标准的日志操作,由 LoggerFactory 来具体确定Logger实现,所以此处主要分析 LoggerFactory代码。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}
1
2
3
4
5
6
7
8
9

getLogger 方法主要有两个步骤:

  1. 获取 ILoggerFactory 的具体实现
  2. 通过 ILoggerFactory 的 getLogger 方法获取真实绑定的日志框架

查看 ILoggerFactory 源码发现它是一个接口并且只有 getLogger 一个方法,所以可以判断在正式的日志框架集成的包中需要实现此接口,并返回适配好的 Logger 的实现,接下来分析 getILoggerFactory() 方法,分析SLF4J具体是怎么查找并绑定到具体的日志框架的

/**
 * Return a logger named according to the name parameter using the
 * statically bound {@link ILoggerFactory} instance.
 *
 * @param name
 *            The name of the logger.
 * @return logger
 */
public static Logger getLogger(String name) {
    // 获取真实的 ILoggerFactory 的具体实现
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

跟踪代码调用可以看到最终是调用方法 performInitialization(); 执行初始化,而 performInitialization() 方法主要是调用 bind() 方法执行最终的绑定的,如果绑定成功后直接调用 getSingleton() 获取单例,然后调用 getLoggerFactory(); 方法获取具体的实现工厂

/**
 * 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:
        // 绑定成功后直接调用 getSingleton() 获取单例,然后调用 getLoggerFactory(); 方法获取具体的实现工厂
        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");
}
 
 
// 执行初始化
private final static void performInitialization() {
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
        // 绑定成功后,执行版本检查,检查SLF4J版本和具体实现的版本是否一致
        versionSanityCheck();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

查看bind() 方法逻辑,发现SLF4J绑定的方式是通过固定约定来是现实的,要求在具体实现包中包含类 org.slf4j.impl.StaticLoggerBinder ,具体约定如下:

  1. 全类名必须是 org.slf4j.impl.StaticLoggerBinder
  2. 必须是单例
  3. 必须包含静态方法 getSingleton(); 获取单例
  4. 必须提供一个static final String REQUESTED_API_VERSION对象指定支持的版本
  5. 必须包含方法 public ILoggerFactory getLoggerFactory() 返回具体的 ILoggerFactory 的实现
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()) {
            // 如果不是Android系统,则通过ClassLoader 查找 org/slf4j/impl/StaticLoggerBinder.class
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            // 如果发现有多个 实现则会抛出 发现多个绑定的信息,此处就是我们在控制台看到的 Class path contains multiple SLF4J bindings.
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }
        // the next line does the binding
        // 通过调用 getSingleton 方法绑定具体的实现
        /// 可能会有多个StaticLoggerBinder,选择哪一个由jvm的类加载决定。
        StaticLoggerBinder.getSingleton();
        // 修改状态为 初始化成功
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        // 报告绑定的 具体的实现是哪个
        reportActualBinding(staticLoggerBinderPathSet);
    } 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.contains("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);
    } finally {
        postBindCleanUp();
    }
}
 
 
// 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);
        }
        while (paths.hasMoreElements()) {
            URL path = paths.nextElement();
            staticLoggerBinderPathSet.add(path);
        }
    } catch (IOException ioe) {
        Util.report("Error getting resources from path", ioe);
    }
    return staticLoggerBinderPathSet;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

至此可以看出SLF4J加载不同的实现的关键为 org.slf4j.impl.StaticLoggerBinder ,以下是 log4j1 集成相关的源码:参考此源码实现及SLF4J加载过程,我们自己可以实现一套基于SLF4J的日志框架

/**
 * Copyright (c) 2004-2011 QOS.ch
 * All rights reserved.
 *
 * Permission is hereby granted, free  of charge, to any person obtaining
 * a  copy  of this  software  and  associated  documentation files  (the
 * "Software"), to  deal in  the Software without  restriction, including
 * without limitation  the rights to  use, copy, modify,  merge, publish,
 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
 * permit persons to whom the Software  is furnished to do so, subject to
 * the following conditions:
 *
 * The  above  copyright  notice  and  this permission  notice  shall  be
 * included in all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package org.slf4j.impl;
 
import org.apache.log4j.Level;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.Util;
import org.slf4j.spi.LoggerFactoryBinder;
 
/**
 * The binding of {@link LoggerFactory} class with an actual instance of
 * {@link ILoggerFactory} is performed using information returned by this class.
 *
 * @author Ceki G&uuml;lc&uuml;
 */
public class StaticLoggerBinder implements LoggerFactoryBinder {
 
    /**
     * The unique instance of this class.
     *
     */
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
 
    /**
     * Return the singleton of this class.
     *
     * @return the StaticLoggerBinder singleton
     */
    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }
 
    /**
     * Declare the version of the SLF4J API this implementation is compiled against.
     * The value of this field is modified with each major release.
     */
    // to avoid constant folding by the compiler, this field must *not* be final
    public static String REQUESTED_API_VERSION = "1.6.99"; // !final
 
    private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();
 
    /**
     * The ILoggerFactory instance returned by the {@link #getLoggerFactory}
     * method should always be the same object
     */
    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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

# SLF4J 1.8之后实现方式

查看源码会发现和1.7之前主要是区别的地方为 bind() 方法,1.8之后采用Java SPI机制来加载不同的日志实现,由于采用了SPI机制,所以SLF4J要求JDK版本要在1.6以上,

private final static void bind() {
    try {
        // 使用JavaSPI机制加载具体实现
        List<SLF4JServiceProvider> providersList = findServiceProviders();
        // 如果发现多个绑定 报告发现多个日志实现
        reportMultipleBindingAmbiguity(providersList);
        // 判断具体实现
        if (providersList != null && !providersList.isEmpty()) {
            // 获取第一个绑定
           PROVIDER = providersList.get(0);
           // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
            // 初始化
           PROVIDER.initialize();
           // 变更状态
           INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
           // 报告具体绑定的日志实现
            reportActualBinding(providersList);
        } else {
            // 如果没有具体实现 修改状态为没有找到具体实现
            INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
            Util.report("No SLF4J providers were found.");
            Util.report("Defaulting to no-operation (NOP) logger implementation");
            Util.report("See " + NO_PROVIDERS_URL + " for further details.");
 
            // 调用1.7 之前的方法,但是不会进行绑定,只是输出提示
            Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
        }
        postBindCleanUp();
    } catch (Exception e) {
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
    }
}
 
 
// 标准Java SPI查找实现
private static List<SLF4JServiceProvider> findServiceProviders() {
    ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
    List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
    for (SLF4JServiceProvider provider : serviceLoader) {
        providerList.add(provider);
    }
    return providerList;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

通过源码可以看出,1.8之后定义了接口 org.slf4j.spi.SLF4JServiceProvider 用于实现SPI的绑定,去除了原先的基于约定的 StaticLoggerBinder 绑定方式

SLF4JServiceProvider接口源码如下:

package org.slf4j.spi;
 
import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.LoggerFactory;
 
/**
 * This interface based on {@link java.util.ServiceLoader} paradigm.
 *
 * <p>It replaces the old static-binding mechanism used in SLF4J versions 1.0.x to 1.7.x.
 *
 * @author Ceki G&uml;lc&uml;
 * @since 1.8
 */
public interface SLF4JServiceProvider {
 
     
    /**
     * Return the instance of {@link ILoggerFactory} that
     * {@link org.slf4j.LoggerFactory} class should bind to.
     *
     * @return instance of {@link ILoggerFactory}
     */
    public ILoggerFactory getLoggerFactory();
     
    /**
     * Return the instance of {@link IMarkerFactory} that
     * {@link org.slf4j.MarkerFactory} class should bind to.
     *
     * @return instance of {@link IMarkerFactory}
     */
    public IMarkerFactory getMarkerFactory();
     
    /**
     * Return the instnace of {@link MDCAdapter} that
     * {@link MDC} should bind to.
     *
     * @return instance of {@link MDCAdapter}
     */
    public MDCAdapter getMDCAdapter();
     
    public String getRequesteApiVersion();
     
    /**
     * Initialize the logging back-end.
     *
     * <p><b>WARNING:</b> This method is intended to be called once by
     * {@link LoggerFactory} class and from nowhere else.
     *
     */
    public void initialize();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

以下为log4j1 集成源码:

org.slf4j.log4j12.Log4j12ServiceProvider

package org.slf4j.log4j12;
 
import org.apache.log4j.Level;
import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.helpers.Util;
import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider;
 
public class Log4j12ServiceProvider implements SLF4JServiceProvider {
 
    /**
     * Declare the version of the SLF4J API this implementation is compiled against.
     * The value of this field is modified with each major release.
     */
    // to avoid constant folding by the compiler, this field must *not* be final
    public static String REQUESTED_API_VERSION = "1.8.99"; // !final
 
    private ILoggerFactory loggerFactory;
    private IMarkerFactory markerFactory;
    private MDCAdapter mdcAdapter;
     
    public Log4j12ServiceProvider() {
        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");
        }
    }
 
    @Override
    public void initialize() {
        loggerFactory = new Log4jLoggerFactory();
        markerFactory = new BasicMarkerFactory();
        mdcAdapter = new Log4jMDCAdapter();
    }
     
    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }
 
    public IMarkerFactory getMarkerFactory() {
        return markerFactory;
    }
 
    public MDCAdapter getMDCAdapter() {
        return mdcAdapter;
    }
 
    public String getRequesteApiVersion() {
        return REQUESTED_API_VERSION;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# Java SPI使用案例

SPI是Service Provider Interface 的简称,即服务提供者接口的意思。说白了就是一种扩展机制,我们在相应配置文件中定义好某个接口的实现类,然后再根据这个接口去这个配置文件中加载这个实例类并实例化。

# SPI机制的约定

  1. 在META-INF/services/目录中创建以接口全限定名命名的文件该文件内容为Api具体实现类的全限定名
  2. 用ServiceLoader类动态加载META-INF中的实现类
  3. 如SPI的实现类为Jar则需要放在主程序classPath中
  4. Api具体实现类必须有一个不带参数的构造方法

# 使用案例

  1. 新建一个Maven工程 spi-interface ,定义一个接口如下:
public interface Printer {
    public void print();
}
1
2
3
  1. 创建Maven工程 spi-good-printer ,定义 Printer 的第一个实现,如下:

public class GoodPrinter implements Printer {
    public void print() {
        System.out.println("good printer");
    }
}
1
2
3
4
5

文件: cc.shixw.spi.api.printer.Printer

cc.shixw.spi.impl.printer.GoodPrinter
1
  1. 创建Maven工程 spi-bad-printer,定义 Printer 的第一个实现,如下:

public class BadPrinter implements Printer {
    public void print() {
        System.out.println("bad printer");
    }
}
1
2
3
4
5

文件: cc.shixw.spi.api.printer.Printer

cc.shixw.spi.impl.printer.BadPrinter
1
  1. 创建Maven工程spi-invoker,引入以上依赖,并创建调用类:
public class MyApp {
 
    public static void main(String[] args) {
        ServiceLoader<Printer> printerServiceLoader = ServiceLoader.load(Printer.class);
        printerServiceLoader.forEach(p-> p.print());
    }
}
1
2
3
4
5
6
7

pom.xml

<dependencies>
    <dependency>
        <groupId>cc.shixw</groupId>
        <artifactId>spi-interface</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>cc.shixw</groupId>
        <artifactId>spi-good-printer</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>cc.shixw</groupId>
        <artifactId>spi-bad-printer</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. 运行MyApp结果如下:

# 参考

SLF4J官网: http://www.slf4j.org/ (opens new window)

SLF4J官方文档: http://www.slf4j.org/docs.html (opens new window)

SLF4J源码: https://github.com/qos-ch/slf4j (opens new window)

LOG4J2源码: https://github.com/apache/logging-log4j2 (opens new window)

编辑 (opens new window)
上次更新: 2021/10/08, 16:56:38
LOG4J使用技巧

← LOG4J使用技巧

最近更新
01
相关资源
11-23
02
Java线程池常用配置
07-23
03
binlog常用操作命令
07-23
更多文章>
Theme by Vdoing | Copyright © 2021-2022 shixw | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式