点击蓝字 关注我们

作者 | 王海林,Apache SeaTunnel PPMC
01
背景
执行引擎 Spark2.x 使用 slf4j + log4j1 Spark3.x 使用 slf4j + log4j2 Flink 使用 slf4j + log4j2 SeaTunnel-Zeta 使用 slf4j + log4j1 SeaTunnel Starter(提交作业的引导器) jar 包跟随作业一起提交 jar 包跟随作业一起提交 Spark-Starter 使用 slf4j + log4j1 Flink-Starter 使用 slf4j + log4j1 SeaTunnel-Starter(SeaTunnel-Zeta) 使用 slf4j + log4j1 Connector 连接器 这里是各种相互冲突的日志桥接包同时引入触发 StackOverflowError 的根源 这里是连接器出现日志冲突的根源 各个 Connertor 可能直接或间接依赖:slf4j、log4j1、log4j2、logback、commons-logging 等 各个 Connector 依赖的第三方包也可能会依赖部分日志桥接包:slf4j-log4j12、log4j-over-slf4j、log4j-to-slf4j、slf4j-jdk14、jul-to-slf4j、slf4j-jcl、jcl-over-slf4j 等 Examples 运行示例 结合上述执行引擎 + Connector 组合出的日志依赖 E2E 测试 结合上述执行引擎 + Connector 组合出的日志依赖
连接器之间(数据源与目标)使用不同的日志框架 连接器与连接器之间的依赖包使用不同的日志框架 执行引擎之间使用不同的日志框架 执行引擎多版本之间使用不同的日志框架 执行引擎与连接器使用不同的日志框架 SeatTunnel Modules 与连接器使用不同的日志框架 以上各种情况相互叠加组合
02
目标
任何连接器组合、运行在任何执行引擎的任何版本上,都能完整输出所有日志(包括各种依赖包中的日志) 即使项目发展变化,连接器增加并更改,也能保持日志正确稳定的输出(只解决一次问题)
动态更改日志级别 串联任务执行过程的全部日志
03
整体设计


解决连接器之间的日志冲突
解决执行引擎之间的日志冲突
seatunnel-core/**-starter,这里是解决不同引擎日志框架差异的最佳位置。我们根据不同执行引擎的自身内置的日志框架不同 ,提供不同的日志桥接包在提交任务时跟随 Connector 一起提交到引擎中去,使得 Connector 及其依赖包中使用其他日志框架输出的日志全部通过桥接包转接到 slf4j-api 上,之后就由每个引擎内置的日志框架承接 slf4j-api 去做日志输出。
Maven管理日志依赖
pom.xml中定义所有日志相关包的缺省 Scope,对所有继承的子模块生效。
pom.xml中定义
maven-shade-plugin规则,在 Starter、Connector 等模块打包阶段 exclude 日志相关 jar 包,当然子模块可以更新需求重新定义(例如 Starter 根据引擎决定 include 的桥接包)。
pom.xml中定义
maven-surefire-plugin规则,在 mvn test 单元测试阶段 exclude 日志相关 jar 包。
SeaTunnel Zeta日志增强
ChildFirstClassLoader,还需要更改此类加载规则使日志相关的包优先从
Parent ClassLoader加载,以保证每次提交任务运行时 Connector 代码中的日志正确的链接到 SeaTunnel-Zeta 配置的日志输出中。
ZT-JID:表示作业(Job)的 ID ZT-TID:表示任务(Task)的 ID ZT-PID:表示任务管道(Pipeline)的 ID
pattern即可在每一行日志都带上任务相关信息:
[%X{ZT-JID, ZT-TID, ZT-PID}] [%p] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c:%L - %m%n
04
实施方法
pom.xml定义 dependencyManagement & dependencies,被所有子模块继承此 Maven Scope 设置
<dependencyManagement>
<!-- ***************** slf4j & provider & bridges start ***************** -->
<!-- Declare slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Declare slf4j-api provider: log4j2.x -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- Declare log4j2 asynchronous loggers provider: disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${log4j2-disruptor.version}</version>
</dependency>
<!-- Include the logging bridges -->
<!-- commons-logging bridge to slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- jdk-logging bridge to slf4j -->
<!-- low performance, see: https://www.slf4j.org/legacy.html#jul-to-slf4j
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
-->
<!-- log4j1.x bridge to log4j2.x -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- Exclude the logging bridges via provided scope -->
<!-- log4j1.x bridge to slf4j
Use of the SLF4J adapter (log4j-over-slf4j) together with the SLF4J bridge (slf4j-log4j12) should never be attempted as it will cause events to endlessly be routed between SLF4J and Log4j 1
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- slf4j binding to log4j1.x -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- log4j2.x binding to slf4j.
Use of the SLF4J adapter (log4j-to-slf4j-2.x.jar) together with the SLF4J bridge (log4j-slf4j-impl-2.x.jar) should never be attempted as it will cause events to endlessly be routed between SLF4J and Log4j 2
-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j2.version}</version>
<scope>provided</scope>
</dependency>
<!-- slf4j binding to jdk-logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- slf4j binding to commons-logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- slf4j binding to nop -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- slf4j binding to simple -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<!-- Exclude other logging provider via provided scope -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
<scope>provided</scope>
</dependency>
<!-- ***************** slf4j & provider & bridges end ***************** -->
</dependencyManagement>
<dependencies>
<!-- ***************** slf4j & provider & bridges start ***************** -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
</dependency>
<!-- ***************** slf4j & provider & bridges end ***************** -->
</dependencies>
pom.xml定义 maven-shade-plugin 规则,所有子模块做 shade 包时继承使用
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<configuration>
<artifactSet>
<excludes>
<exclude>org.slf4j:*</exclude>
<exclude>ch.qos.logback:*</exclude>
<exclude>log4j:*</exclude>
<exclude>org.apache.logging.log4j:*</exclude>
<exclude>commons-logging:*</exclude>
</excludes>
</artifactSet>
</configuration>
</plugin>
pom.xml定义 maven-surefire-plugin 规则用于各个模块单元测试使用
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<classpathDependencyExcludes>
<!--
The logger provider & bridges declared under 'provided' scope should be explicitly excluded from testing as below.
-->
<classpathDependencyExclude>org.slf4j:slf4j-jdk14</classpathDependencyExclude>
<classpathDependencyExclude>org.slf4j:slf4j-jcl</classpathDependencyExclude>
<classpathDependencyExclude>org.slf4j:slf4j-nop</classpathDependencyExclude>
<classpathDependencyExclude>org.slf4j:slf4j-simple</classpathDependencyExclude>
<classpathDependencyExclude>org.slf4j:slf4j-reload4j</classpathDependencyExclude>
<classpathDependencyExclude>org.slf4j:slf4j-log4j12</classpathDependencyExclude>
<classpathDependencyExclude>org.slf4j:log4j-over-slf4j</classpathDependencyExclude>
<classpathDependencyExclude>commons-logging:commons-logging</classpathDependencyExclude>
<classpathDependencyExclude>log4j:log4j</classpathDependencyExclude>
<classpathDependencyExclude>ch.qos.logback:logback-classic</classpathDependencyExclude>
<classpathDependencyExclude>ch.qos.logback:logback-core</classpathDependencyExclude>
<classpathDependencyExclude>org.apache.logging.log4j:log4j-to-slf4j</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<artifactSet>
<excludes>
<!--
Spark(2.x) server lib already include:
slf4j-api
log4j
slf4j-log4j12
jul-to-slf4j
jcl-over-slf4j
Spark(3.x) server lib already include:
slf4j-api
log4j-api
log4j-core
log4j-slf4j-impl
log4j-1.2-api
jul-to-slf4j
jcl-over-slf4j
-->
<exclude>org.slf4j:slf4j-api</exclude>
<exclude>org.slf4j:slf4j-jdk14</exclude>
<exclude>org.slf4j:slf4j-jcl</exclude>
<exclude>org.slf4j:slf4j-nop</exclude>
<exclude>org.slf4j:slf4j-simple</exclude>
<exclude>org.slf4j:slf4j-reload4j</exclude>
<exclude>org.slf4j:slf4j-log4j12</exclude>
<exclude>org.slf4j:jcl-over-slf4j</exclude>
<exclude>org.slf4j:jul-to-slf4j</exclude>
<!-- spark2.x use slf4j + log4j1.x -->
<exclude>org.slf4j:log4j-over-slf4j</exclude>
<exclude>log4j:*</exclude>
<exclude>commons-logging:*</exclude>
<exclude>ch.qos.logback:*</exclude>
<exclude>org.apache.logging.log4j:log4j-api</exclude>
<exclude>org.apache.logging.log4j:log4j-core</exclude>
<exclude>org.apache.logging.log4j:log4j-slf4j-impl</exclude>
<!-- spark3.x use slf4j + log4j2.x -->
<exclude>org.apache.logging.log4j:log4j-to-slf4j</exclude>
</excludes>
</artifactSet>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<artifactSet>
<excludes>
<!--
not excluded:
jcl-over-slf4j(commons-logging to slf4j bridge)
Flink server lib already include:
slf4j-api
log4j-api
log4j-core
log4j-slf4j-impl
log4j-1.2-api
-->
<exclude>org.slf4j:slf4j-api</exclude>
<exclude>org.slf4j:slf4j-jdk14</exclude>
<exclude>org.slf4j:slf4j-jcl</exclude>
<exclude>org.slf4j:slf4j-nop</exclude>
<exclude>org.slf4j:slf4j-simple</exclude>
<exclude>org.slf4j:slf4j-reload4j</exclude>
<exclude>org.slf4j:slf4j-log4j12</exclude>
<exclude>org.slf4j:log4j-over-slf4j</exclude>
<exclude>log4j:*</exclude>
<exclude>commons-logging:*</exclude>
<exclude>ch.qos.logback:*</exclude>
<exclude>org.apache.logging.log4j:log4j-api</exclude>
<exclude>org.apache.logging.log4j:log4j-core</exclude>
<exclude>org.apache.logging.log4j:log4j-slf4j-impl</exclude>
<exclude>org.apache.logging.log4j:log4j-1.2-api</exclude>
<exclude>org.apache.logging.log4j:log4j-to-slf4j</exclude>
</excludes>
</artifactSet>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<artifactSet>
<excludes>
<!--
not excluded:
slf4j-api
log4j2-api
log4j2-core
log4j-slf4j-impl
log4j-1.2-api(log4j1.x to log4j2.x bridge)
jcl-over-slf4j(commons-logging to slf4j bridge)
-->
<exclude>org.slf4j:slf4j-jdk14</exclude>
<exclude>org.slf4j:slf4j-jcl</exclude>
<exclude>org.slf4j:slf4j-nop</exclude>
<exclude>org.slf4j:slf4j-simple</exclude>
<exclude>org.slf4j:slf4j-reload4j</exclude>
<exclude>org.slf4j:slf4j-log4j12</exclude>
<exclude>org.slf4j:log4j-over-slf4j</exclude>
<exclude>log4j:*</exclude>
<exclude>commons-logging:*</exclude>
<exclude>ch.qos.logback:*</exclude>
<exclude>org.apache.logging.log4j:log4j-to-slf4j</exclude>
</excludes>
</artifactSet>
</configuration>
</plugin>
05
完整的功能issue&PR
https://github.com/apache/incubator-seatunnel/issues/2725 https://github.com/apache/incubator-seatunnel/pull/3025 https://github.com/apache/incubator-seatunnel/pull/2722
06
参考
https://www.slf4j.org/manual.html#swapping https://www.slf4j.org/legacy.html https://logging.apache.org/log4j/2.x/manual/migration.html
Apache SeaTunnel

往期推荐
SeaTunnel 2.3.1重磅发布!重构后AI Compatible特性让ChatGPT自动生成Connector代码
点击阅读原文,点亮Star⭐️!

文章转载自SeaTunnel,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




