概念
SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。
实际上,SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类。从某种程度上,SLF4J有点类似JDBC,不过比JDBC更简单,在JDBC中,你需要指定驱动程序,而在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用
那个具体的日志系统。如同使用JDBC基本不用考虑具体数据库一样,SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
使用条件
如果你开发的是类库或者嵌入式组件,那么就应该考虑采用SLF4J,因为不可能影响最终用户选择哪种日志系统。在另一方面,如果是一个简单或者独立的应用,确定只有一种日志系统,那么就没有使用SLF4J的必要。假设你打算将你使用log4j的产品卖给要求使用logback的用户时,面对成千上万的log4j调用的修改,相信这绝对不是一件轻松的事情。但是如果开始便使用SLF4J,那么这种转换将是非常轻松的事情。
说白了,slf4j和common-logging一个意思,就是简单的日志门面,方便我们在不动代码的前提下随意切换我们的日志框架。在部署的时候,选择不同的日志系统包,就可自动转换到不同的日志系统上。
比如:选择JDK自带的日志系统,则只需要将slf4j-api-1.5.10.jar和slf4j-jdk14-1.5.10.jar放置到classpath中即可,如果中途无法忍受JDK自带的日志系统了,想换成log4j的日志系统,仅需要用slf4j-log4j12-1.5.10.jar替换slf4j-jdk14-1.5.10.jar即可。(当然也需要log4j的jar及配置文件)。当然如果这个时候觉得log4j的性能不是太好,出于性能考虑想换成logback的日志系统的话,也只是需要将logback的core包和classic包替换原来的log4j包就OK(当然也需要logback的配置文件)。
实战
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.linkinpark.commons</groupId>
<artifactId>linkin-log-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>linkin-log-test</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- slf4j依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<!-- log4j依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- logback依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
<!-- slf4j自带的简单日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.12</version>
</dependency>
<!-- jdk自带的日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.12</version>
</dependency>
<!-- common-logging日志框架 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
log4j.propetites配置文件:
log4j.rootLogger=DEBUG,console
# 以下是rootLogger的配置,子类默认继承,但是子类重写下面配置=rootLogger+自己配置,我晕
#输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#设置输出样式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#日志输出信息格式为
log4j.appender.console.layout.ConversionPattern=[%-d{yyyy-MM-dd HH:mm:ss}]-[%t-%5p]-[%C-%M(%L)]: %m%n
logback配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true" scan="true" scanPeriod="30 seconds">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Java测试代码:
package org.linkinpark.commons.slf4j;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*/
public class Slf4jTest
{
private static Logger logger = LoggerFactory.getLogger(Slf4jTest.class);
@Test
public void test()
{
logger.debug("debug()方法,看下这里logger的实例是:{}", logger.getClass());
logger.info("info()方法,看下这里logger的实例是:{}", logger.getClass());
logger.error("error()方法,看下这里logger的实例是:{}", logger.getClass());
}
}
1,我们来一个一个测试,假如我们现在不想用任何的额外的日志框架,只想用slf4j来输出日志。slf4j也给我们提供了一个简单的simple日志框架。注释掉别的pom依赖,只打开slf4j-simple的依赖。运行上面的测试控制台输出如下:
[main] INFO org.linkinpark.commons.slf4j.Slf4jTest - info()方法,看下这里logger的实例是:class org.slf4j.impl.SimpleLogger
[main] ERROR org.linkinpark.commons.slf4j.Slf4jTest - error()方法,看下这里logger的实例是:class org.slf4j.impl.SimpleLogger
OK,没问题,观察日志输出,我们也看到了这个时候slf4j使用的是slf4j自带的日志简单日志。
2,现在我们想使用JDK自带的日志框架来输出日志。去掉pom文件中多余的日志框架的依赖,然后添加slf4j-jdk14的依赖,运行上面的测试控制台输出如下:
三月 01, 2016 1:51:44 下午 org.linkinpark.commons.slf4j.Slf4jTest test
信息: info()方法,看下这里logger的实例是:class org.slf4j.impl.JDK14LoggerAdapter
三月 01, 2016 1:51:44 下午 org.linkinpark.commons.slf4j.Slf4jTest test
严重: error()方法,看下这里logger的实例是:class org.slf4j.impl.JDK14LoggerAdapter
OK,没问题,观察日志输出,我们也看到这个时候slf4j使用的是JDK自带的日志框架,实际运行中在上面的slf4j-jdk14中有一个桥接类,slf4j用该类桥接到了JDK自带的日志框架中。
3,现在我们想使用log4j来输出日志。去掉pom文件中多余的日志框架的依赖,然后添加slf4j-log4j12和log4j2个依赖到pom中,运行上面的测试控制台输出如下:
log4j:WARN No appenders could be found for logger (org.linkinpark.commons.slf4j.Slf4jTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
OK,错误已经很明显了,没有找见log4j的配置文件,所以没有appender来控制日志的输出。我们将log4j.propertites配置文件丢在我们项目根路径下继续运行上面的测试:
[2016-03-01 13:56:15]-[main-DEBUG]-[org.linkinpark.commons.slf4j.Slf4jTest-test(19)]: debug()方法,看下这里logger的实例是:class org.slf4j.impl.Log4jLoggerAdapter
[2016-03-01 13:56:15]-[main- INFO]-[org.linkinpark.commons.slf4j.Slf4jTest-test(20)]: info()方法,看下这里logger的实例是:class org.slf4j.impl.Log4jLoggerAdapter
[2016-03-01 13:56:15]-[main-ERROR]-[org.linkinpark.commons.slf4j.Slf4jTest-test(21)]: error()方法,看下这里logger的实例是:class org.slf4j.impl.Log4jLoggerAdapter
OK,没问题,观察日志输出,我们也看到这个时候slf4j使用了log4j来做日志框架。和上面第2点类似,这里slf4j也是用到了一个名叫Log4jLoggerAdapter的桥接类桥接到log4j的。
4,现在我们想使用logback来输出日志。去掉pom文件中多余的日志框架的依赖,添加logback-core和logback-classic的依赖到pom中,运行上面的测试,控制台输出如下:
13:59:29.797 [main] DEBUG o.linkinpark.commons.slf4j.Slf4jTest - debug()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger
13:59:29.801 [main] INFO o.linkinpark.commons.slf4j.Slf4jTest - info()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger
13:59:29.802 [main] ERROR o.linkinpark.commons.slf4j.Slf4jTest - error()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger
OK,日志正常输出,但是我们发现了这里使用的其实的logback自带的默认的控制台输出的简单的日志类,我们现在添加logback.xml到项目的classpath中,继续运行测试:
14:00:43,794 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
14:00:43,795 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
14:00:43,795 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/Users/LinkinPark/WorkSpace/linkin-log-test/target/classes/logback.xml]
14:00:43,861 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - Setting ReconfigureOnChangeFilter scanning period to 30 seconds
14:00:43,861 |-INFO in ReconfigureOnChangeFilter{invocationCounter=0} - Will scan for changes in [[/Users/LinkinPark/WorkSpace/linkin-log-test/target/classes/logback.xml]] every 30 seconds.
14:00:43,861 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - Adding ReconfigureOnChangeFilter as a turbo filter
14:00:43,863 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
14:00:43,865 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
14:00:43,882 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
14:00:43,910 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
14:00:43,911 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
14:00:43,911 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
14:00:43,912 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@5ebec15 - Registering current configuration as safe fallback point
14:00:43.916 [main] DEBUG o.linkinpark.commons.slf4j.Slf4jTest - debug()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger
14:00:43.919 [main] INFO o.linkinpark.commons.slf4j.Slf4jTest - info()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger
14:00:43.920 [main] ERROR o.linkinpark.commons.slf4j.Slf4jTest - error()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger
OK,没问题,观察日志输出,我们看到这个时候slf4j使用logback来做日志框架。其实slf4j和logback也是相处最融洽的2套日志管理框架,建议以后使用这2个的组合。
小结
通过上面这个比较详细的例子,我们看到了,我们使用slf4j来统一管理的我们的代码,我们不停的切换了多种日志框架来作为我们的日志输出,但是我们的业务代码,Java类中的那些日志输出代码一点都不用去改,这也真是slf4j最迷人的地方。它完美的整合了自己的一个简单日志,JDK自带的日志,log4j,logback,common-logging。只不顾我们在转换日志输出的时候,可能会用到一些中间的桥接jar包。
当然,我仔细有看过这些桥接类的maven依赖,比如:
slf4j-log4j12:它本身就会依赖slf4j-api和log4j,maven依赖的jar包是可以传递的,所以也可以不用人工的去添加这些jar包的。
slf4j-jcl:它本身就会依赖slf4j-api和common-logging,maven依赖的jar包是可以传递的,所以也可以不用人工的去添加这些jar包的。
slf4j-jdk14:它本身就会依赖slf4j-api,maven依赖的jar包是可以传递的,所以也可以不用人工的去添加这些jar包的。
slf还有一个比较直接的吸引人的地方就是Java代码中输出日志的Java写法,性能很好,如果配合logback使用据说是log4j性能的10倍,特别是有字符串连接的时候。
在使用Commons Logging时,我们经常会看到以下方法的写法:
if (logger.isDebugEnabled())
{
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
存在isDebugEnabled()的判断逻辑是为了在避免多余的字符串拼接,即如果不存在isDebugEnabled()判断,即使当前日志级别为ERROR时,在遇到logger.info()调用时,它还会先拼接日志消息的字符串,然后进入该方法内,才发现这个日志语句不用打印。而这种多余的拼接不仅浪费了多余的CPU操作,而且会增加GC的负担。SLF4J则提供以下的方式来解决这个问题:
logger.info("Loading XML bean definitions from {}", encodedResource.getResource());
网友评论