SLF4j概述
上篇文章写了日志门面介绍和JCL使用,作为一个曾经的主流日志门面技术,JCL终究因为设计时的产生问题所限,导致至今已没什么人使用,而一款优秀的日志门面技术SLF4j,吸取了JCL的教训与经验,出现在java开发者的视野中,开始被广泛关注和使用。
这里如果谈谈历史还蛮好玩的,涉及到一位Java日志领域知名人物Ceki Gülcü,log4j、slf4j、logback都是出自他之手,在Apache组织开发的log4j,后来离开了apache开发了logback,apache反而又推出了log4j2与之竞争,感兴趣的可以去看看Java日志系统历史从入门到崩溃 - 个人文章 - SegmentFault 思否
SLF4J,即简单日志门面(Simple Logging Facade for Java),也为Java日志访问提供了一套标准、规范的API框架,它支持了log4j和logback,它本身也提供了功能较简单的日志实现,但一般很少用到,它的设计思想和JCL较为不同,要么遵循slf4j规范开发,要么使用适配器来完成slf-api与具体日志实现框架的整合。
SLF4j使用
maven依赖
<!-- slf4j日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- slf4j内置的简单实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
日志绑定
SLF4J使用指定的日志框架需要通过“日志绑定”,通过一些jar文件实现。
绑定具体的日志实现框架:
1. 绑定已经实现了sf4的日志框架直接添加对应依赖
2. 绑定没有实现sf4的日志框架先添加日志的适配器再添加实现类的依赖
logback、slf4j-simple遵循了slf4j-api的开发,只需要导入对应依赖。
slf4j-nop是关闭日志功能。
如果绑定了多个日志实现,使用第一个找到的日志实现。

例:绑定log4j
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
桥接
slf4j的强大之处还在于它对遗留代码的支持,如果项目之前使用的是例如Log4j等日志框架实现,可以通过slf4j提供的“桥接器”将其转移至slf4j使用。甚至还可以桥接JCL。

例如:log4j代码通过桥接器转移至logback实现,首先移除log4j依赖,然后添加桥接器:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.12</version>
</dependency>
这里会出现一个问题比如log4j-over-slf4j
与slf4j-log4j12
共存出现stack overflow,就是因为slf4jlog4j死循环
特性
核心类:
- Logger:日志记录器,用于写日志
- LoggerFactory:日志记录器的工厂类,用于获取日志记录器。
日志级别:
- trace
- debug
- info
- warn
- error
比JCL还少一个fatal级别。
使用占位符输出日志信息:
public static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class);
@Test
public void testQuick() {
String name = "rhett";
LOGGER.info("名字:{}",name);
}
将系统异常信息输出:
Logger提供了带throwable参数的方法重载,可以便捷地输出错误信息:
public static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class);
@Test
public void testException(){
try{
int i = 1/0;
}catch (Exception e){
LOGGER.error("出现异常:",e);
}
}
SLF4J原理
这个阶段推荐自己进行源码跟踪,我大概把方法栈贴一下
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());//!!!
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();//通往bind方法
}
}
}
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");
}
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()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();//!!!
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
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();
}
}
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;
}
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
注意: 虽然StaticLoggerBinder
是在org.slf4j.impl
包下,但日志门面jar包中并无此类,此类是由桥接器实现的。
桥接器的原理其实就是在其jar包中存在StaticLoggerBinder.class,这样就可在运行时动态绑定底层的日志实现框架。
然后回到bind方法:
StaticLoggerBinder.getSingleton();
获取了一个StaticLoggerBinder
的单例对象
然后返回getILoggerFactory方法:
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
总结:桥接器会调用的日志框架实现的相关代码生成其内部的Logger(此Logger与org.slf4j.Logger)不兼容,再通过适配器包装日志框架实现内部的Logger.
原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e6%97%a5%e5%bf%97%e9%97%a8%e9%9d%a2slf4j%e4%bb%8b%e7%bb%8d%e5%92%8c%e4%bd%bf%e7%94%a8/