常见java日志系统的搭配详解:关于slf4j log4j log4j2 logback jul jcl commons-logging jdk-logging

时间:2021-09-26 22:03:31

先看一张图:

常见java日志系统的搭配详解:关于slf4j log4j log4j2 logback jul jcl commons-logging jdk-logging

 

是不是有点晕, 晕就对了。这个仅仅是 slf4j 的情况,实际上, 我们不仅要接触到 slf4j ,有时候还会接触其他的日志系统。且看下文分解。

 

1 直接使用各个日志系统

1.1 直接使用log4j

最开始的时候, 我们都是使用log4j, 怎么使用呢? 先引入jar,log4j-1.x.x  jar

maven是这样的:

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

然后配置: 配置文件主要是 log4j.properties, 具体略

然后代码中使用

static org.apache.log4j.Logger logger = org.apache.log4j.LogManager.getLogger(TestLog.class);

1.2 直接使用JCL,即commons-logging

有时候我们也使用Apache的 commons-logging ,也就是 Jakarta Commons Logging,简称 JCL。commons-logging其实也是一个日志 接口,不是一个日志控件,没有日志功能,它只是统一了JDK Logging与Log4j的API,并把日志功能交给JDK Loggings或者是log4j 。

commons-logging能够选择使用Log4j还是JDK Logging,但是他不依赖Log4j,JDK Logging的API。如果项目的classpath中包含了log4j的类库,就会使用log4j,否则就使用JDK Logging。使用commons-logging能够灵活的选择使用那些日志方式,而且不需要修改源代码。

怎么使用呢? 首先需要一个 commons-logging-1.2.jar, 然后引入 log4j的类库,或者不引入直接JDK Logging(这里的 log4j的类库 应该是 指log4j-1.x , 对于 log4j2 不知道是否支持 )

参考 :https://blog.csdn.net/u011794238/article/details/50747953 

maven是这样的:

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

配置:主要是 commons-logging.properties,具体略

关于 commons-logging ,其实也是一个 slf4j 类似的日志接口系统,至于其原理,请参考其他的博客,

 

1.3 直接使用log4j2

后面出了个log4j2,log4j2是怎么使用的? 还是先引入jar : log4j-api-2.x.x.jar和log4j-core-2.x.x.jar

maven是这样的:

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>

没错,引入一个 log4j-core 即可, log4j-core  会自动引入 log4j-api,另外我发现这么一个jar:

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>2.8.2</version>
        </dependency>

仔细看一下,这个仅仅是一个pom 不是jar,也就是 log4j-core 的parent 。

然后配置: 配置文件主要是 log4j2.xml ( 有时候也可以是 log4j.xml, 或其他名字 ), 具体略

然后代码中使用

static org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(TestLog.class);

注意看到 log4j 、log4j2 的差别还是比较大的, log4j需要一个 jar,log4j-1.x.x  jar ,而且是 1.x.x 的格式, 最高好像也就是1.7.25 , 我是没有看到 log4j-2.x的 jar 的。 但是到了 log4j2,jar 的命名发生了很大的变化: log4j-api-2.x.x.jar和log4j-core-2.x.x.jar ,没有 log4j-2.x .jar , 而且也不是一个jar ,是分开了2个。

 

1.4 直接使用logback

后面又来了一个 logback, logback 是天生就和slf 紧密结合在一起的,无法分开。(观察发现logback也没有提供任何的 Logger 实现,logback-core 的功能主要就是实现了很大的 appender ,pattern 等)一般我们需要引入logback-classic , 它直接依赖了 logback-core、slf4j-api , 所以,我们需要3个jar 。

maven是这样的:

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

然后配置:  配置文件主要是 logback.xml , 具体略

然后代码中使用,(只能通过slf4j 的api )

static Logger logger = LoggerFactory.getLogger(TestLog.class);

 

1.5 直接使用JUL ,也就是jdk-logging

直接用 jul 的人应该很少了吧。不过这里还是要说明一下。具体 怎么使用呢?首先,它是jdk 自带的,不需要引入jar:

然后配置: 默认配置文件是logging.properties,logging.properties 一般位于 jdk或者jre 的lib 目录, 一个 jdk或者jre 只有一个配置。具体略

然后代码中使用

static java.util.logging.Logger logger = java.util.logging.Logger.getLogger(TestLog.class.getName());

logger.info("in");

logger.severe("severe");

logger.fine("fine");

 

可以看到它的 Logger  是  java.util.logging 包(简称 JUL)下面的, 是jdk 自带的。 不过需要注意的是 JUL的Logger 提供的方法和其他的框架的还不一样。

它的日志是这样的:

四月 11, 2019 1:52:46 下午 com.lk.TestLog main
信息: in
四月 11, 2019 1:52:46 下午 com.lk.TestLog main
严重: severe

可以看到其中的月份和 日志级别都使用了 汉字, 非常的明显的 特征。

 

2 使用slf4j 日志接口框架

前面都是直接使用的方式(除了logback), 如果我们统一到 slf4j 呢?这里说的是统一到 slf4j是指 代码中使用 slf4j的API。 首先我们要明白,slf4j 是什么? 它是一个统一的日志接口框架,这样说可能还是有些懵逼。 怎么说呢, 可以简单理解为它提供了几个关键的接口,但是没有实现他们。 所以说它只是一个 日志接口。通常我们需要 slf4j 结合其他日志框架来打印我们的日志。 老实说,这样做TM有些奇特, 这样做的主要目的是 方便的替换 我们日志框架的实现, 而不用修改代码—— 其实这样的需求也是非常少的。

不管怎么说,slf4j 已然成为了主流。

 

几乎所有的其他日志框架都可以作为 slf4j 日志接口的实现。怎么使用呢? 通常我们需要首先引入 slf4j-api-1.x.x.jar ( 目前来看, 主要有 1.5,1.6,1.7  三个实现,1.5 有些特别, 兼容性不是很好), 然后引入具体的实现的 jar。 

 

有哪些实现?

2.1 slf4j-api 的NOP实现

NOP, 也就是No Operation,也就是不做任何操作。不需要引入任何其他jar,配置文件也是无。

 

2.2 slf4j-api 的logback实现

logback,前文已述

 

2.3 slf4j-api 的simple实现

simple,意味着简单。它非常简单的实现, 需要引入slf4j-simple-1.7.5.jar,

maven是这样的:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.12</version>
        </dependency>

slf4j-simple 依赖了 slf4j-api 。slf4j-simple 通常只是把日志直接打印到 控制台。

配置是:simplelogger.properties 或者通过D系统参数。

 

2.4 slf4j-api 的JUL实现

其实就是把日志操作 转接给了 jdk , 也就是 jul , 需要 slf4j-jdk14-1.x.x.jar ,

maven是这样的:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>

slf4j-jdk14 依赖了 slf4j-api 。

配置同直接使用JUL的场景

 

2.5 slf4j-api 的log4j 实现

具体日志工作交给了 log4j , 需要 slf4j-log4j12-1.x.x.jar (主要是 slf4j-log4j12-1.6.x.jar, slf4j-log4j12-1.7.x.jar), log4j-1.2.x.jar ,

maven是这样的:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>

slf4j-log4j12 依赖了slf4j-api 和 log4j12 。

配置同直接使用log4j的场景

 

2.6 slf4j-api 的log4j2 实现

依赖 log4j2 , 需要 log4j-slf4j-impl-2.x.x.jar , log4j-api ,  log4j-core ,

maven是这样的:


<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.8.2</version>
</dependency>

log4j-slf4j-impl 依赖了slf4j-api 和 log4j-api,  但是并没有依赖   log4j-core , 因此, 我们需要手动引入 它 。

配置同直接使用log4j2的场景

 

2.7 slf4j-api 的JCL实现

依赖 commons-logging , 需要 slf4j-jcl ,

maven :用得少,略

配置:略

 

3 使用其他日志框架,然后桥接给 slf4j 日志接口框架

变态的还不仅仅是这个, 有时候,我们还需要 反向的 日志操作。比如 我们有代码已经直接使用的 log4j 的api, 但是想使用 slf4j 的实现,怎么办呢? 可以 先引入  log4j 的api的jar, 然后引入 slf4j-api , 然后引入 slf4j 的具体实现(如上), 比如 log4j  的接口,slf4j 做桥接, logback 的实现, 这个是完全可以的。 (这可以达到一种奇特的效果, 整个系统中, 虽然有各种第二方、三方框架, 他们直接使用各种各样的 日志api, 但是我都可以让他们 统一使用 logback的实现, 也就是 只使用一个 配置即可: logback.xml , 可以减少配置量)

但是, 我们肯定不会说, log4j  的接口,slf4j 做桥接, 然后又采用 log4j  作为slf4j  的实现。看起来行得通, 实际上会出现 循环引用的问题, 

这种做法,其实有点绕,容易让人迷糊。也许是我见识短浅,我其实没有直接使用过这样做法。 不过, 现在中 确实也有这样的需求。

 

具体来说 也有很多种, log4j, log4j2, jcl,jul,等等。但是 我们似乎找不到 logback 桥接给 slf4j  的情况,我感觉是因为logback 没有直接使用的接口,它天生就是和slf4j 紧密联系的。

3.1 使用log4j的 api,然后桥接给slf4j 

对于 log4j, 我们首先需要log4j的 api接口: log4j-over-slf4j-1.7.x.jar, 其他的就交给了 slf4j  ————  你可能有疑问, log4j 的接口在哪里呢? 没错,就是  log4j-over-slf4j-1.7.x.jar ,这里我们没有使用 log4j-1.x.x  jar,因为 log4j-1.x.x  jar 包含了 log4j 的实现,我们不需要它,我们需要排除它。 这种做法简直不可思议, 不过它就是这么发生了!

maven是这样的:

        <dependency>
            <groupId>org.slf4j</groupId>
            <version>1.7.21</version>
            <artifactId>log4j-over-slf4j</artifactId>
        </dependency>

log4j-over-slf4j 依赖了slf4j-api。

至于桥接到slf4j 之后的工作,比如配置文件啊,还需要的其他jar 啊,请参考前文。总之, 不要出现了 循环引用即可。

 

循环引用

另外,我注意到, 如果 log4j-over-slf4j  、  log4j-1.x.x  jar 如果同时存在于 maven 的pom ,或者classpath, 那么 到底是启用哪一个呢?  对应 maven, 答案是, 哪个先出现在pom中 就启用哪一个!

需要重要的是: log4j-over-slf4j.jar 不能和 slf4j-log4j12.jar 同时存在于 classpath 中, 否则

对于 slf4j-log4j12-1.7.25.jar :出现

Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting *Error.

slf4j-log4j12-1.7.25.jar  比较温和,有检测机制, slf4j-log4j12-1.7.25.jar 之前的 slf4j-log4j12-1.x.x jar 则是直接抛出 异常:

java.lang.*Error
    at java.util.HashMap.hash(HashMap.java:338)
    at java.util.HashMap.get(HashMap.java:556)
    at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:67)
    at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:358)
    at org.apache.log4j.Category.<init>(Category.java:57)
    at org.apache.log4j.Logger.<init>(Logger.java:37)
    at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:43)
    at org.apache.log4j.LogManager.getLogger(LogManager.java:45)

除了log4j, 我想其他日志框架也是如此,不能出现over-slf4j 之后出现循环委托的情况。

 

3.2 使用log4j2的 api,然后桥接给slf4j 

 log4j 是 1.x 时代了, 如果是 log4j2 呢? 我们首先需要log4j2的 api接口:log4j-to-slf4j-2.x.x.jar, 

maven是这样的:

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.11.1</version>
</dependency>

log4j-to-slf4j-2.x.x.jar 依赖了slf4j-api 和log4j-api ( log4j-api 是log4j2 的api 接口 ), log4j-to-slf4j-2.x.x.jar 没有依赖 log4j-core。 那么 log4j2 是怎么反向桥接到 slf4j 上去的呢? 其实这个是 log4j-to-slf4j 的功劳,log4j-to-slf4j-2.x.x.jar 桥接了 log4j-api ,然后具体实现交给了 slf4j。 

特别注意到:log4j-1.x 的桥接jar 名是这样的:  log4j-over-slf4j  而log4j-2.x 的桥接jar 名是这样的: log4j-to-slf4j,  名字非常相近。

至于桥接到slf4j 之后的工作,请参考前文。

 

3.3 使用JCL 的 api,然后桥接给slf4j 

前面说过使用 SLF4J的API, 然后 使用JCL的实现, 反过来也是可以的。

jcl 并不是jul, 不是 java.util.logging 而是 org.apache.commons.logging , 也就是 commons-logging 的那一套。 为此, 我们需要一个 jcl-over-slf4j-1.x.x.jar

maven是这样的:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>

至于桥接到slf4j 之后的工作,请参考前文。

 

3.4 使用JUL 的 api,然后桥接给slf4j 

JUL 和 JCL 是仅仅一字之别,但是千万不能搞混啊!它是将 jdk logging API 打印的日志交给了 slf4j,我们 需要一个 jul-to-slf4j-1.7.x.jar 

maven是这样的:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.25</version>
</dependency>

 代码中还需要做一些修改:

需要先执行下面的:

    static{
        SLF4JBridgeHandler.install();
    }

不过从打印的结果, 我看到 日志打印了两次, 似乎哪里出了问题,并没有达到想要的效果。

四月 11, 2019 6:40:12 下午 com.lk.TestLog main
信息: in
18:40:12,318 [com.lk.TestLog] - severe           --- log4j的日志 
四月
11, 2019 6:40:12 下午 com.lk.TestLog main 严重: severe

需要注意的是 我们并没有 jul-over-slf4j 的jar, 这个可能是命名的历史原因, 暂时 jul-to-slf4j 可以理解为就是  jul-over-slf4j ... 

至于桥接到slf4j 之后的工作,请参考前文。

 

4 使用A日志框架接口,然后桥接给 B日志框架实现

这样的需求也是有的,做法肯定也是有的,也肯定有人实现了的。比如,貌似 feign 也有一个实现,gossip 也有:

常见java日志系统的搭配详解:关于slf4j log4j log4j2 logback jul jcl commons-logging jdk-logging

这些jar我没有用过,具体,就不多说了。

 

关于SLF4J 其实也可以写很多,还有JCL( 也就是commons-logging)具体的原理这里不多讲了,请参考其他的博客。总之,感觉这整个java 的日志系统 还是挺麻烦的,特别是其中的jar的命名,不太规律,容易混淆,不容易记住。搞来搞去, 容易把人搞晕呢,这个时候 就需要看其源码! 其实其中很多jar 的实现是很简单的,也就几个类,特别是那些桥接的jar, 一打开看一下就明白了,哦,说得也是呢!

 

 看我的文章后是不是有所收获呢?是不是应该点32个赞鼓励一下呢?

 

 

参考:

https://my.oschina.net/pingpangkuangmo/blog/410224
https://www.slf4j.org/legacy.html
https://blog.csdn.net/u011794238/article/details/50747953
https://blog.csdn.net/john1337/article/details/76152906
http://www.cnblogs.com/chen310/p/4216316.html
https://blog.csdn.net/u011794238/article/details/50783188
https://blog.csdn.net/u011794238/article/details/50771488