slf4j 绑定机制浅析

简介

Simple Logging Facade for Java, 顾名思义,它是作为一个许多logging库(如log4j,logback,commons-logging等)的简单门面,提供一个简单统一的接口,从而使得最终用户能够很方便的使用和切换想要的logging实现。

可能你还是要问, 为什么要使用slf4j呢?
举个例子,如果你直接用log4j来写日志,你的代码里有100个地方是这样做的。
现在你想换成logback或其他的日志实现,你就需要修改100个地方。
而如果使用slf4j来写入日志,因为多了一层绑定的过程,你只要切换你的依赖即可。

使用slf4j很简单,只要加入slf4j-api-xxx.jar、第三方日志实现及其binding包的依赖即可。

<!-- slf4j api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>

<!-- logback binding -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>

<!-- logback impl -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>

接下来, 你就可以打印 Hello World 啦!

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

根据slf4j的介绍,

each SLF4J binding is hardwired at compile time to use one and only one specific logging framework

它是静态绑定的, 接下来,让我们一起来揭开绑定的神秘面纱吧。

绑定原理

这是官网的一张图, 这里展示了常见的一些log实现和绑定:

前面说过, slf4j是在静态绑定, 为什么这么说呢? 答案就在绑定实现的包中, 如下:

logback-classic.jar

slf4j-log4j.jar

如上图,可以发现都有org.slf4j.impl.StaticLoggerBinder,
再看LoggerFactory.getLogger()时,即org.slf4j.LoggerFactory.getILoggerFactory的调用hierarchy:

其中bind()内部是我们想要的:


private final static void bind() {
try {
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
emitSubstituteLoggerWarning();
} 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.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
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);
}
}

如代码,绑定方法bind(),首先通过findPossibleStaticLoggerBinderPathSet():staticLoggerBinderPathSet来获取classpath中可能的binder paths, 如下:

Enumeration<URL> ClassLoader.getSystemResources(String STATIC_LOGGER_BINDER_PATH)

其中STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"。

多个绑定存在的情况

当发生多个绑定同时存在时,官方的说明是这样的:

The warning emitted by SLF4J is just that, a warning. Even when multiple bindings are present, SLF4J will pick one logging framework/implementation and bind with it. The way SLF4J picks a binding is determined by the JVM and for all practical purposes should be considered random.

实际运行结果并结合以上代码,当返回的URL有多个时,reportMultipleBindingAmbiguity()会打印出警告:

Class path contains multiple SLF4J bindings.
Found binding in [jar:file:/home/niko/.m2/repository/.../logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class[]
Found binding in [...]

然后reportActualBinding()再打印出真正被加载的StaticLoggerBinder的class name,比如:

Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]

为了避免这种问题,可以使用maven的全局排除依赖来保证只会出现某一个实现。

more info -> http://www.slf4j.org/codes.html

MDC (Mapped Diagnostic Context )

这个功能只有log4j和logback支持。
具体内容请查看log4j和logback部分。