java 的 logger 太多东西了无法解释为什么就稍微进行了整理

稍微介绍一下 java 处理 log 的东西

特别是 slf4j 的种类太多,或许也没有人能很好的说明各自用处。产生混乱的原因有七成是由 slf4j 造成的。这种混乱的局面进行了简单整理。主要分为三个类型的 jar。

1、 接口(Interface)

主要有这些

  • commons-logging
  • slf4j

他们也就只提供了接口的功能,并没实现 log 的输出功能。
接口要是太老的话,自己提供的功能也会有不足的地方。这些 jar 虽然含有有 log 输出的功能,但只是简单的实现。

2、 适配器(Adapter)

主要有这些

  • jcl-over-slf4j.XXX.jar(把 commons-logging 的处理交给 slf4j 来处理)
  • jul-to-slf4j.XXX.jar(把 java.util.logging 的处理交给 slf4j 来处理)
  • log4j-over-slf4j.XXX.jar(把 log4j 的处理交给 slf4j 来处理)

适配器的作用是,代理各接口与实际处理 logger 的任务。从外部看起来都是相同的方法,但是内部却是各自来实现自己的功能。除了处理 log 之外,也有『slf4j-jdk14』这样的兼容不同 JDK 版本来处理的适配器。正因为有了适配器,即使在 commons-logging、log4j 中追加了 slf4j 的时候也能进行工作。

适配器通过设置文件来进行适配,log4j.jar 不存在的时候会读取 log4j.xml 文件。适配器是很认真的来工作的。但是因为有了适配器来进行各种处理,这也是造成了 jar 文件混乱而使得 log 的输出复杂的原因。

另外,「jul」是「java.util.logging」の简称,「jcl」是「Jakarta Commons Logging」的简称而不是「Java Class Library」的简称。这些简称的理解困难也是造成 log 的输出复杂的原因之一。

3、 实现

主要有这些

  • java.util.logging
  • log4j
  • logback

logback 是 log4j 的开发者开发出来的下一代 log 处理。通过对接口的实现,如果做得不好的话会造成处理速度变慢,或者就没有对接口提供的方法进行处理。

slf4j 适配器
  • slf4j-log4j12-XXXX.jar(把 slf4j 的处理交给 log4j1.2 来处理)
  • slf4j-jdk14-XXX.jar(在jdk1.4中,把 slf4j 的处理交给 java.util.logging 来处理)
  • slf4j-nop-XXX.jar(全部的 log 内容都不输出)
  • slf4j-simple-XXX.jar(log 等级为 INFO 以上的内容输出到 System.err 中)
  • slf4j-log4j-XXX.jar(把 slf4j 的处理交给 log4j 来处理)
  • slf4j-jcl-XXX.jar(把 slf4j 的处理交给 commons.logging 来处理)

使用『slf4j』的时候被称为适配器的 jar 有很多个,这是为了使用 slf4j 的时候选择不同的实现方法,因此在 classpath 中不能含有多个。如果有多个适配器的时候,slf4j 无法知道该交给那个适配器来进行处理。
而使用 slf4j + logback 的时候是不需要适配器的,只需要用 slf4j-api。

concrete-bindings

这是 slf4j 官网的图释,然而完全不知道什么是什么。。。

使用 slf4j + logback

接下来让我们试着从『commons-logging + log4j』变到『slf4j + logback』。手动管理 jar 有点困难,下面以使用 maven 来进行讲解。

添加 slf4j 与 logbak 的依赖

在 pom.xml 中添加下面两个依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
去除 commons-logging 与 log4j 的依赖

这个处理实在是很麻烦,无法在 maven 中简单的就去掉,需要对每个 jar 进行去除的处理。这是因为有很多 jar 包中依赖了这两个东西。如果是使用 jdk1.5 以后的话 slf4j-jdk14 也是可以一起删除掉的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<artifactId>slf4j-jdk14</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>

这个处理使用 eclipse 的 m2e 插件的话可以比较简单的进行。打开 pom.xml 文件,再打开 Dependency Hierarchy 标签,在 Filter 里输入这两个 jar 的名字的话,就会表示出全部依赖的 jar 包。接下来右键点击 jar 包选择「Exclude Maven Artifact…」就可以了。

最终我们需要的 jar 包如下

  • slf4j-api-XXX.jar
  • logback-classic-XXX.jar
  • logback-core-XXX.jar
  • jcl-over-slf4j.XXX.jar
  • jul-to-slf4j.XXX.jar
  • log4j-over-slf4j.XXX.jar

虽然说有点多,其实必要的 jar 包只有最初的三个。

替换 log 的配置文件

log4j.xml、log4j.dtd、log4j.properties 不再需要了直接删掉。而使用的『logback.xml』文件参照下面的例子适当修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<prudent>true</prudent>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>

<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>

<root>
<level value="info" />
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>

如果使用了旧的写法的话tomcat启动的时候会出现警告,这时候需要参考 Logback Error Codes 来进行修改。

从网上的消息来看 ch.qos.logback.classic.PatternLayout 已经是 DEPRECATED(不推荐使用)了,修改这里就好。encoder 与 layout 没有进行自定义化,这两个不指定 class 也没关系。虽然这有点麻烦,但这样就能从旧的方式变成新的方式了吧。

jar 中存在之前处理 log 时出现的情况的处理

即使是按照之前的方法变成了 slf4j+logback 的时候,如果还存在之前处理的log的jar包的话,会出现「Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory」这样的问题。这时候就需要使用帅气的适配器来处理了。

所谓的把原来的方法交出来处理,就是使用之前说的适配器『交给 slf4j 来处理』。比如依赖了 commons-logging 却出现了 NoClassDefFoundError 的话,把 jcl-over-slf4j.XXX.jar 加入就行。

  • jcl-over-slf4j.XXX.jar(把 commons-logging 的处理交给 slf4j 来处理)
  • jul-to-slf4j.XXX.jar(把 java.util.logging 的处理交给 slf4j 来处理)
  • log4j-over-slf4j.XXX.jar(把 log4j 的处理交给 slf4j 来处理)

把所有的东西交给 slf4j 来处理,之后再变成 slf4j => logback 的形式来处理的话,就能完全的进行转移了。

简单的使用方法

大概就是如下所示,jcl 的话也差不多。

1
2
3
4
5
6
7
8
9
10
11
12
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Hoge {

private static Logger LOGGER = LoggerFactory.getLogger(Hoge.class);

// 「あいう」と出力されます
public static void main(String[] args) {
LOGGER.info("あ{}う", "い");
}
}

{}是占位符。从 import 来看的话只有 slf4j,logback 相关的 import 都没有。虽然只写了 slf4j 的代码,然而 slf4j 会从 classpath 中找到 logback,然后使用它来实现。虽然表面上看不出来,但是还是的使用了 logback 来进行处理,所以请各位放心。

logger 的变迁

实现
  1. log4j v1 版本的流行
  2. log4j 的作者重新开发出 logback
  3. log2j v2 版本出现 <- 现在的时间点

是这样的感觉,觉得 logback 已经成熟的时候,log4j 的 version 2 就出现了,变成了无法解决谁是业内标准的状态。

接口
  1. commons.logging
  2. SLF4J

接口只有这两个。总之 SLF4J 貌似成为了业内标准,所以使用 SLF4J 的话不会有什么问题。说到 slf4j 的适配器,或许会出现在今后的 JDK1.8 中像 stream、lambda 内部使用的 logger,或者是处理不同的JDK的差异的实现变得更多都说不定。

从这么多 logger 想到的

接口、适配器、实现,这些都虽说是 java 的东西,从个人来说的话 『合并成一个jar,通过设定文件来改变动作的话就好了』。要是按照现在的话每当新的 logger 出现的时候,又会增加适配器,从而 jar 包也会变多。

虽说各 jar 分开不发生耦合的道理也是正确的,然而从现状来看的话无论是谁都觉得是混乱的状态,不理解 slf4j 而使用的话就会在 tomcat 启动的时候出现

1
Multiple bindings were found on the class path

这样的错误,谁都不会注意到这样的错误的项目有很多。
(这个时候 slf4j 会通过 /dev/null 把输出给取消因此 slf4j 不会生效,只剩下 log4j 来进行处理)

古人说过『自由即是不自由』,比起自由还是希望得到能注重单纯的东西。如果要注重性能的话,那或许还是让同一个公司来制作的话是最好的结果吧。

via javaのロガーが多すぎて訳が解らないので整理して