shade logback 的正确姿势

示例工程参照 aliyun-mq/rocketmq-logging

在开发 RocketMQ 5.0 SDK 的过程中,遭遇到一个问题,由于历史上的原因,RocketMQ 的日志默认情况下是会输出到用户的账户主目录下的,但是在之前的版本中,RocketMQ 自己维护了一个日志模块,也是我们所熟知的 logging 模块(下图所示)。之所以没有使用开源的 slf4j 和 logback/log4j2 的方案主要还是想要避免配置文件和环境参数的冲突。

RocketMQ 作为经常被业务代码所依赖的中间件产品,如果自己也直接使用开源的 logback/log4j2 解决方案,那么和用户自己的配置发生冲突几乎是板上钉钉的事情。但是 RocketMQ 本身这个日志模块带来一定的维护成本不说,功能也是无法做到和开源的 logback/log4j2 的解决方案对齐的。

在 Java 中,对依赖进行 shade 和 relocation 是解决依赖冲突的标准姿势,不过一般只会 relocate 一下 java 的包名,那么除此之外,是否还可以替换被 shade 的 logback 的配置文件和各个参数呢?答案是可以的。

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
72
 <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>false</minimizeJar>
<createSourcesJar>true</createSourcesJar>
<shadeSourcesContent>true</shadeSourcesContent>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
<relocations>
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>${shade-prefix}.shaded.org.slf4j</shadedPattern>
</relocation>
<relocation>
<pattern>ch.qos.logback</pattern>
<shadedPattern>${shade-prefix}.ch.qos.logback</shadedPattern>
</relocation>
<relocation>
<!-- logback internal constant, see
ch.qos.logback.classic.util.ContextInitializer -->
<rawString>true</rawString>
<pattern>^logback.configurationFile$</pattern>
<shadedPattern>${config-file-prefix}.logback.configurationFile</shadedPattern>
</relocation>
<relocation>
<!-- shades logback internal constant, see
ch.qos.logback.classic.util.ContextInitializer -->
<rawString>true</rawString>
<pattern>^logback.groovy$</pattern>
<shadedPattern>${config-file-prefix}.logback.groovy</shadedPattern>
</relocation>
<relocation>
<!-- shades logback internal constant and rename user's logback file, see
ch.qos.logback.classic.util.ContextInitializer -->
<rawString>true</rawString>
<pattern>^logback(-test)?.xml$</pattern>
<shadedPattern>${config-file-prefix}.logback$1.xml</shadedPattern>
</relocation>
<relocation>
<!-- logback internal constant, see ch.qos.logback.classic.ClassicConstants -->
<rawString>true</rawString>
<pattern>^logback.ContextSelector$</pattern>
<shadedPattern>${config-file-prefix}.logback.ContextSelector</shadedPattern>
</relocation>
<relocation>
<!-- logback internal constant, see ch.qos.logback.classic.ClassicConstants -->
<rawString>true</rawString>
<pattern>^java:comp/env/logback/configuration-resource$</pattern>
<shadedPattern>java:comp/env/${config-file-prefix}.logback/configuration-resource
</shadedPattern>
</relocation>
<relocation>
<!-- logback internal constant, see ch.qos.logback.classic.ClassicConstants -->
<rawString>true</rawString>
<pattern>^java:comp/env/logback/context-name$</pattern>
<shadedPattern>java:comp/env/${config-file-prefix}.logback/context-name
</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>

其中 $shade-prefix 表示 shade 之后 package 名的前缀,$config-file-prefix 表示 shade 之后配置文件基于 logback.xml 的前缀,比如对于 rocketmq 而言,此时配置文件的前缀也就变成了 rocketmq.logback.groovy/rocketmq.logback-test.xml/rocketmq.logback.xml 了。可以达到与用户的配置文件 logback.groovy/logback-test.xml/logback.xml 不冲突的目的。相应的,原生的 logback 中一些环境参数也会被替换成 rocketmq 的前缀。

不过我们在 shade logback 的同时也要记得 shade slf4j,因为如果只 shade 了 logback 中 ch.qos.logback 开头的类的话,那么 shade 之后的 logback 还是基于没有 shade 的 slf4j 进行实现的。那么此时 logback-shaded 和 logback 同时被引入的话,就会作为 slf4j 的两个实现,从而冲突了的,因此我们在 shade logback 的时候也要 shade 一份 slf4j。这样 logback-shaded 是 slf4j-shaded 的实现,标准 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
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>false</minimizeJar>
<createSourcesJar>true</createSourcesJar>
<shadeSourcesContent>true</shadeSourcesContent>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
<relocations>
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>${shade-prefix}.shaded.org.slf4j</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>

值得提醒的是,使用 shade 之后 logback 的话,配置文件中使用的 appender 类也要都替换成 shade 之后的类名。

这里 shade 的方法主要是参考了 glowroot, 👏👏👏 感谢提供灵感。