OK,现在我们来使用slf4j。
- 概念
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的配置文件)。
- OK,现在举一个完整的例子。下面先贴出完整的代码:
pom文件:
<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
<?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;/** * @创建作者: LinkinPark * @创建时间: 2016年3月1日 * @功能描述: slf4j测试类 */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.Logger13:59:29.801 [main] INFO o.linkinpark.commons.slf4j.Slf4jTest - info()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger13: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 seconds14: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 filter14: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] property14:00:43,910 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG14: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 point14:00:43.916 [main] DEBUG o.linkinpark.commons.slf4j.Slf4jTest - debug()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger14:00:43.919 [main] INFO o.linkinpark.commons.slf4j.Slf4jTest - info()方法,看下这里logger的实例是:class ch.qos.logback.classic.Logger14: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个的组合。
5,现在我们想使用common-logging来输出日志。前面关于common-logging我也已经整理过了,如果项目的classpath中存在log4j的包,那么久使用log4j,如果不存在则使用JDK自带的logging。slf4j同样支持common-logging来作为日志输出。
去掉pom文件中多余的日志框架的依赖,添加slf4j-jcl和commons-logging的依赖到pom中。
这里我们先暂时不添加log4j到我们的项目,运行上面的测试,控制台输出如下:
三月 01, 2016 2:05:18 下午 org.linkinpark.commons.slf4j.Slf4jTest info信息: info()方法,看下这里logger的实例是:class org.slf4j.impl.JCLLoggerAdapter三月 01, 2016 2:05:18 下午 org.linkinpark.commons.slf4j.Slf4jTest error严重: error()方法,看下这里logger的实例是:class org.slf4j.impl.JCLLoggerAdapter
OK,没问题,现在slf使用common-logging来输出日志,这里slf用了一个JCLLoggerAdapter来做桥接,桥接到了common-logging包的日志输出去的。由于这里的项目没有log4j的包,所以common-logging使用JDK的logging来输出
现在我们添加log4j到我们的项目中,继续运行上面的测试,控制台输出如下:
[2016-03-01 14:06:56]-[main-DEBUG]-[org.slf4j.impl.JCLLoggerAdapter-debug(186)]: debug()方法,看下这里logger的实例是:class org.slf4j.impl.JCLLoggerAdapter [2016-03-01 14:06:56]-[main- INFO]-[org.slf4j.impl.JCLLoggerAdapter-info(281)]: info()方法,看下这里logger的实例是:class org.slf4j.impl.JCLLoggerAdapter [2016-03-01 14:06:56]-[main-ERROR]-[org.slf4j.impl.JCLLoggerAdapter-error(471)]: error()方法,看下这里logger的实例是:class org.slf4j.impl.JCLLoggerAdapter
OK,没问题,现在控slf正常的使用了log4j来作为common-logging的实现来输出了日志。观察日志输出,我们也发现整理并不是直接使用的log4j,而是使用的JCLLoggerAdapter桥接到common-logging,然后common-logging自己选择的log4j来控制的输出,这点要注意,和上面的第3点不一样的,别搞混淆了。
- 总结:
通过上面这个比较详细的例子,我们看到了,我们使用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());
OK,本来不打算研究slf4j的源码的,但是在整理博客的过程中越来越惊奇于slf4j的设计,所以还是认真的整理下slf4j的源码吧。现在我手里权威点的关于slf4j的资料没有,所以我去看官网得了,然后下载下原来来研究下源码,之后会整理关于slf4j源码解析的相关博客,这篇就先到这里吧。