示例工程参照 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 的配置文件和各个参数呢?答案是可以的。
| 12
 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 的实现,两者互相不冲突。
| 12
 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, 👏👏👏 感谢提供灵感。