每一个java程序员都知道日志对于任何一个java应用程序,尤其是服务端程序是至关重要的,而很多程序员也已经熟悉各种不同的日志库如java.util.logging、apache log4j、logback,但如果你还不知道slf4j(simple logging facade for java)的话,那么是时候去在你项目中学习使用slf4j了。
slf4j不同于其他日志类库,与其它有很大的不同。slf4j(simple logging facade for java)不是一个真正的日志实现,而是一个抽象层( abstraction layer),也可以理解为一个接口,它是一种适配器的实现方式,它本身不具有输出日志的功能,输出日志还是由log4j、logback等这样的日志组件来进行输出。如下图描述slf4j和log4j的关系
下面我来举个场景,现在开发项目都是使用maven进行构建开发,假设架构师a开发了一个order.jar通用组件,他在程序中使用的是log4j组件进行日志输出;程序员b自己之前一直在开发自己的业务模块,并且他在程序中使用的是logback日志组件,突然有一天程序员b需要在自己的业务系统中使用架构师a的order.jar通用组件,这个时候问题就出现了,由于两套程序使用了不同的日志组件,程序员b除了要维护自己的logback日志组件配置,还需要维护order,jar中的日志组件配置,这个问题是很头疼的。其实解决这一切问题也不是什么难事,使用slf4j就可以顺利解决。
总的来说,slf4j使你的代码独立于任意一个特定的日志api,这是一个对于开发api的开发者很好的思想。虽然抽象日志类库的思想已经不是新鲜的事物而且apache commons logging也已经在使用这种思想了,但现在slf4j正迅速成为java世界的日志标准,让我们再看看几个使用slf4j而不是log4j、logback或者java.util.logging的理由。
slf4j对比log4j,logback和java.util.logging的优势
正如我之前说的,在你的代码中使用slf4j写日志语句的主要出发点是使得你的程序独立于任意特定的日志类库,依赖于特定类可能需要不同与你已有的配置,并且导致更多维护的麻烦。
除此之外,还有一个slf4j api的特性使得我坚持使用slf4j而抛弃我长期间钟爱的lof4j的理由,是被称为占位符(place holder),在代码中表示为“{}”的特性。
占位符是一个非常类似于在string的format()方法中的%s,因为它会在运行时被某个提供的实际字符串所替换。这不仅降低了你代码中字符串连接次数,而且还节省了新建的string对象。
即使你可能没需要那些对象,但这个依旧成立,取决于你的生产环境的日志级别,例如在debug或者info级别的字符串连接。因为string对象是不可修改的并且它们建立在一个string池中,它们消耗堆内存( heap memory)而且大多数时间他们是不被需要的,例如当你的应用程序在生产环境以error级别运行时候,一个string使用在debug语句就是不被需要的。
通过使用slf4j,你可以在运行时延迟字符串的建立,这意味着只有需要的string对象才被建立。而如果你已经使用log4j,那么你已经对于在if条件中使用debug语句这种变通方案十分熟悉了,但slf4j的占位符就比这个好用得多。
如下面所示使用log4j的日志解决方案,其中有多出字符串拼接,不仅降低了可读性也带来了性能上的诸多问题,
if (logger.isdebugenabled()) {
logger.debug("processing trade with id: " + id + " symbol: " + symbol);
}
在来看看使用slf4j的解决方案:
logger.debug("processing trade with id: {} and symbol : {} ", id, symbol);
在使用slf4j的时候我们不需要来拼接字符串,因此也就避免了内存消耗。如果在字符串中存在多个占位符我们也可以将变量放到一个数组中,就像下面
logger.debug("processing trade with id: {} and symbol : {} ", new object[]{id, symbol});
而且使用slf4j的另一个好处就是在最终日志信息的字符串之前,debug或者info方法会检查一个特定的日志级别是不是打开了,这不仅降低了内存消耗而且预先降低了cpu去处理字符串连接命令的时间,如下:
public void debug(string format, object arg1, object arg2) {
if (logger.isdebugenabled()) {
formattingtuple ft = messageformatter.format(format, arg1, arg2);
logger.log(fqcn, level.debug, ft.getmessage(), ft.getthrowable());
}
}
尽管slf4j提供了这么丰富的api,同时也将性能问题帮我们考虑进去,但是在最终的生产环境生还是要尽量只输出有必要的日志,毕竟会有io问题。
怎么用slf4j做log4j的日志记录
使用slf4j不仅需要引入slf4j-api.jar,同时还需要引用对应的日志组件,这取决与你程序用使用的日志组件类别,如下:
<dependency>
<groupid>org.slf4j</groupid>
<artifactid>slf4j-api</artifactid>
<version>1.7.13</version>
</dependency>
<dependency>
<groupid>org.slf4j</groupid>
<artifactid>slf4j-log4j12</artifactid>
<version>1.7.13</version>
</dependency>
其中slf4j-api是slf4j必须的jar,slf4j-log4j12是log4j的组件。
在程序中使用如下
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
首先要引入slf4j.Logger和slf4j.LoggerFactory,这点很重要,因为对应的log4j也有这两个类,接着如下:
private static final Logger log = LoggerFactory.getLogger(MyTest.class);
在类文件中创建log对象,接着如下:
log.debug("");
log.info("");
log.warn("");
log.error("");
在程序中快乐的使用。
总结
个人建议使用slf4j的而不是直接使用 log4j, commons logging, logback 或者 java.util.logging,因为这样可以让你的程序适具有更多的扩展性。
1. 在你的开源或内部类库中使用slf4j会使得它独立于任何一个特定的日志实现,这意味着不需要管理多个日志配置或者多个日志类库,以后别人调用你的工具包时也可以不用关心日志组件问题。
2. slf4j提供了基于占位符的日志方法,减少了在String拼接时的性能开销问题。并且,通过使用slf4j的日志方法,你可以延迟构建日志信息(srting)的开销,意味着程序可以有更高的吞吐性能。
最后
扫描下方Q群二维码快速加入Java学习交流群
关注下方‘程序员周刊’微信公共帐号,每周获取最新IT资讯。