feitu的博客

slf4j和logback在maven中的配置

slf4j和logback的简单介绍

关于slf4j的介绍很多,文本不再多言,在笔者看来,slf4j主要解决两个问题,通过下面具体事例来说明。

1.我们正在开发一个bar.jar组件,并且使用了common-logging作为日志,这种情况下,使用我们组件的第三方程序也不得不引入了common-logging,为了让common-logging能正常的工作,第三方的程序也不得加一些额外的配置,显然,第三方程序不太喜欢这种方式。

slf4j这样的日志组件正是为了解决这个问题,其解决思路是:开发时面向日志的标准接口,具体使用何种日志实现留给最终的组件调用者。像上面的例子是,开发bar.jar时使用slf4j的日志标准,而不引用任何一种实现,第三方程序需要用到bar.jar再决定使用何种具体的实现。目前使用这种“标准-实现”的日志框架中,sfl4j的使用最为广泛,slf4j提供了一个日志实现logback,另外,为了能兼顾众多的具体日志实现,slf4j支持了commons-logging,log4j,log4j2等。

2.我们的项目引入了一个组件,例如叫做foo.jar,foo.jar使用了log4j作为其日志,而我们自己的项目使用slf4j+logback作为日志的解决方式,那么这样一来,项目中继承了foo.jar所依赖的log4j, 而且foo.jar中日志很可能无法正常的运行,不会将日志写到为logback配置的日志文件中。

为了解决这个问题,slf4j提供了adapter的方式用一个伪装的组件替换掉了这些日志组件(本人认为这是适配器模式一个很好的例子),这个伪装的日志组件将日志导向了slf4j的具体实现,在上面的例子里,是将log4j导向了logback,对于常见的commons-logging,log4j,java.util.logging, slf4j都提供了adapter,对于slf4j没有提供adapter的日志组件,也可以根据其标准自己编写。

以上是slf4j和logback的简单介绍,下面着重介绍在maven中的配置。

在maven中的配置

1.加入标准和实现

首先,在pom.xml中加入sfl4j的标准和使用实现,这里使用logback作为具体实现,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
<scope>runtime</scope>
</dependency>

2.针对其他依赖引入的log组件,加入slf4j adapter,并排除其原有依赖

以spring-core为例,其使用commons-logging作为日志组件,组需要加入slf4j commons-logging adapter,还需要将spring-core的commons-logging依赖排除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.4.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>

像cassandra-all这样的组件更要注意,因为其不仅依赖了第三方日志,还依赖了slf4j的其他实现,下面这例子中,如果不排除掉slf4j-log4j12,logback有可能会被顶替掉,slf4j会采用在classpath中找到的第一个符合slf4j实现的日志组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId> org.apache.cassandra</groupId>
<artifactId>cassandra-all</artifactId>
<version>0.8.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>

总结起来说,当引入一个新的组件时,我们要仔细的检查其依赖的日志组件,检查的要点有这么两个

  • 如果其依赖了log4j,commons-logging,java.util,需要将其排除,并加入对应的adapter
  • 如果依赖了slf4j及其实现,需要全部排除

3.一个”一劳永逸”的解决方式
像是上面这种每次引入新的依赖都要检查日志组件和adapter的方式确实有些麻烦,可以在一开始的时候就在pom.xml中加入slf4j提供的所有adapter,并且使用maven-enforcer-plugin这个maven插件禁止除了logback之外的slf4j实现。一个比较完整的写法如下

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<dependencies>
<!-- slf4j api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<!-- slf4j提供的logback实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
<scope>runtime</scope>
</dependency>
<!-- slf4j提供的adapter -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<!-- 其他依赖 -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce-banned-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<!-- 禁用slf4j除了logback之外的其他依赖 -->
<bannedDependencies>
<excludes>
<exclude>org.slf4j:slf4j-log4j12</exclude>
<exclude>org.slf4j:slf4j-jdk14</exclude>
<exclude>org.slf4j:slf4j-nop</exclude>
<exclude>org.slf4j:slf4j-simple</exclude>
<exclude>org.slf4j:slf4j-jcl</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

注意maven-enforcer-plugin的作用不是全局排除依赖,而是一个保险机制:检查依赖是否存在,如果存在,则构建失败。

当然,这种方式也是有缺点的:一是有可能引入一些没用的依赖,第二,如果引入了一个不是slf4j提供的、而是自定义的slf4j实现,依然会导致和logback的冲突。

写到这里本文差不多结束了,敲这些字的过程中不禁想,一个简单的log组件的配置想搞明白也不是那么简单,开发软件,其实真是挺难的。