slf4j的使用有两种方式,一种是混合绑定(concrete-bindings), 另一种是桥接遗产(bridging-legacy).
5.1 混合绑定(concrete-bindings)
concrete-bindings模式指在新项目中即开发者直接使用sl4j的api来打印日志, 而底层绑定任意一种日志框架,如logback, log4j, j.u.l等.
混合绑定根据实现原理,基本上有两种形式, 分别为有适配器(adapter)的绑定和无适配器的绑定.
有适配器的混合绑定是指底层没有实现slf4j的接口,而是通过适配器直接调用底层日志框架的Logger, 无适配器的绑定不需要调用其它日志框架的Logger, 其本身就实现了slf4j的全部接口.
几个混合绑定的包分别是:
- slf4j-log4j12-1.7.21.jar(适配器, 绑定log4j, Logger由log4j-1.2.17.jar提供)
- slf4j-jdk14-1.7.21.jar(适配器, 绑定l.u.l, Logger由JVM runtime, 即j.u.l库提供)
- logback-classic-1.0.13.jar(无适配器, slf4j的一个native实现)
- slf4j-simple-1.7.21.jar(无适配器,slf4j的简单实现, 仅打印INFO及更高级别的消息, 所有输出全部重定向到System.err, 适合小应用)
以上几种绑定可以无缝切换, 不需要改动内部代码. 无论哪种绑定,均依赖slf4j-api.jar.
此外, 适配器绑定需要一种具体的日志框架, 如log4j绑定slf4j-log4j12-1.7.21.jar依赖log4j.jar, j.u.l绑定slf4j-jdk14-1.7.21.jar依赖j.u.l(java runtime提供); 无适配器的直接实现, logback-classic依赖logback-core提供底层功能, slf4j-simple则不依赖其它库.
以上四种绑定的示例图如下:
下面来分析两个典型绑定log4j和logback的用法.
①log4j适配器绑定(slf4j-log4j12)配置:
<!--pom.xml-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
注意: 添加上述适配器绑定配置后会自动拉下来两个依赖库, 分别是slf4j-api-1.7.21.jar和log4j-1.2.17.jar
基本逻辑: 用户层 <- 中间层 <- 底层基础日志框架层
org.slf4j.impl.Log4jLoggerFactory <- StaticLoggerBinder.getSingleton().getLoggerFactory()<-
org.sl4j.impl.StaticLoggerBinder.getSingleton().getLoggerFactory() <- 具体的日志框库的Logger
其中org.slf4j.impl.Log4jLoggerFactory在应用层调用, StaticLoggerBinder在中间层实现, 获取具体的日志框库的Logger
绑定实例图如下:
应用层(slf4j-api-1.7.21.jar)
用户调用org.slf4j.impl.Log4jLoggerFactory.getLogger获取底层具体的Logger:
// Foo.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Foo {
private final static Logger logger = LoggerFactory.getLogger(CommuteNaviInfoParser.class);
public static void main() {
logger.info("info:{}..", "hello, sl4j");
}
}
适配层(slf4j-log4j12-1.7.21.jar)由应用层org.slf4j.impl.Log4jLoggerFactory.getLogger内部创建适配层的StaticLoggerBinder:
public static Logger getLogger(Class<?> clazz) {
return StaticLoggerBinder.getSingleton().getLoggerFactory();
}
接下来直接由StaticLoggerBinder获取具体的Logger:
private StaticLoggerBinder() {
loggerFactory = new Log4jLoggerFactory();
}
public Log4jLoggerFactory() {
// force log4j to initialize
org.apache.log4j.LogManager.getRootLogger();
}
注意: 各个StaticLoggerBinder均在适配层实现, 放在org.slf4j.impl中.
② slf4j绑定到logback-classic上
配置:
<!--pom.xml-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
注意: 添加上述适配器绑定配置后会自动拉下来两个依赖库, 分别是slf4j-api-1.7.21.jar和logback-core-1.0.13.jar
logback-classic没有适配器层, 而是在logback-classic-1.0.13.jar的ch.qos.logback.classic.Logger直接实现了slf4j的org.slf4j.Logger, 并强依赖ch.qos.logback.core中的大量基础类:
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.spi.LocationAwareLogger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.util.LoggerNameUtil;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.spi.AppenderAttachable;
import ch.qos.logback.core.spi.AppenderAttachableImpl;
import ch.qos.logback.core.spi.FilterReply;
public final class Logger implements org.slf4j.Logger,
LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {}
绑定示例图:
5.2 桥接遗产(bridging-legacy)
桥接遗产用法主要针对历史遗留项目, 不论是用log4j写的, j.c.l写的,还是j.u.l写的, 都可以在不改动代码的情况下具有另外一种日志框架的能力.
比如,你的项目使用java提供的原生日志库j.u.l写的, 使用slf4j的bridging-legacy模式,便可在不改动一行代码的情况下瞬间具有log4j的全部特性.
说得更直白一些,就是你的项目代码可能是5年前写的, 当时由于没得选择, 用了一个比较垃圾的日志框架, 有各种缺陷和问题, 如不能按天存储, 不能控制大小, 支持的appender很少, 无法存入数据库等. 你很想对这个已完工并在线上运行的项目进行改造, 显然, 直接改代码, 把旧的日志框架替换掉是不现实的, 因为很有可能引入不可预期的bug.
那么,如何在不修改代码的前提下, 替换掉旧的日志框架,引入更优秀且成熟的日志框架如如log4j和logback呢? slf4j的bridging-legacy模式便是为了解决这个痛点.
slf4j以slf4j-api为中间层, 将上层旧日志框架的消息转发到底层绑定的新日志框架上.
基于不同的底层框架,以SLF4J作为中转层,有如下几种组合用法:
基于j.u.l的facade使用
上述facade将slf4j-api.jar绑定到底层基础日志库j.u.l(jvm runtime)上. slf4j-api和底层日志库的Logger通过适配器连接.
基于logback-classic的facade使用
基于log4j的facade使用
举例说明上述facade的使用, 以便于大家理解.
假如我有一个已完成的使用了旧日志框架commons-loggings的项目,现在想把它替换成log4j以获得更多更好的特性.
项目的maven旧配置如下:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
项目代码:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Created by xialeizhou on 16/9/20.
*/
public class MainTest {
private static Log logger = LogFactory.getLog(MainTest.class);
public static void main(String[] args) throws InterruptedException {
logger.info("hello,world");
}
}
项目打印的基于commons-logging的日志显示在console上,具体如下:
十月 23, 2016 6:52:00 下午 MainTest main
信息: hello,world
下面我们对项目改造, 将commongs-logging框架的日志转发到log4j上. 改造很简单, 我们将commongs-logging依赖删除, 替换为相应的facade(此处为jcl-over-slf4j.jar), 并在facade下面挂一个5.1的混合绑定即可.
具体来讲, 将commons-logging.jar替换成jcl-over-slf4j.jar, 并加入适配器slf4j-log412.jar(注意, 加入slf4j-log412.jar后会自动pull下来另外两个jar包), 所以实际最终只需添加facadejcl-over-slf4j.jar和5.1节混合绑定中相同的jar包slf4j-log412.jar即可.
改造后的maven配置:
<!--facade-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<!--binding-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
现在, 我们的旧项目在没有改一行代码的情况下具有了log4j的全部特性, 下面进行测试.
在resources/下新建一个log4j.properties文件, 对commongs-logging库的日志输出进行定制化:
# Root logger option
log4j.rootLogger=INFO, stdout, fout
# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.Threshold = INFO
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# add a FileAppender to the logger fout
log4j.appender.fout=org.apache.log4j.FileAppender
# create a log file
log4j.appender.fout.File=royce-testing.log
log4j.appender.fout.layout=org.apache.log4j.PatternLayout
# use a more detailed message pattern
log4j.appender.fout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
重新编译运行, console输出变为:
2016-10-23 19:26:15 INFO MainTest:11 - hello,world
同时在当前目录生成了一个日志文件:
% cat royce-testing.log
INFO 2016-10-23 19:26:15,341 0 MainTest [main] hello,world
可见, 基于facade的日志框架桥接已经生效, 我们再不改动代码的前提下,让commons-logging日志框架具有了log4j12的全部特性.