Spark升级中对log4j中的一些思考
背景
最近在做Spark版本的升级(由spark3.1升级到spark3.5),其实单纯从spark升级涉及到的log4j来说,并没有什么能够记录的,
但是由于公司内部做了Spark的serveless,把spring和spark混在了一起,所以导致了不可预见的问题
分析
我们Spring用的是5.2.6.RELEASE版本,由于spark用的是logback作为日志的具体实现,而Spark在3.1和spark 3.5是采用了不同的日志具体实现:
在 spark3.1中采用的是log4j1 (log4j + slf4j-log4j2),spark 3.5中采用的是log42(log4j-core + log4j-api + log4j-slf4j2-impl),
再加上由于其他log包引入,比如说:
1. "org.apache.logging.log4j" % "log4j-1.2-api"
2. "org.apache.logging.log4j" % "log4j-to-slf4j"
3. "org.slf4j" % "slf4j-log4j12"
4. "org.slf4j" % "slf4j-api"
5. "org.slf4j" % "jcl-over-slf4j"
6. "org.slf4j" % "jul-to-slf4j"
7. "commons-logging" % "commons-logging"
8. "ch.qos.logback" % "logback-classic"
9. "ch.qos.logback" % "logback-core"
这真是太酸爽了,一时间几乎日志系统都齐全了,出现的错误也是千奇百怪:
1. Getting Exception org.apache.logging.slf4j.SLF4JLoggerContext cannot be cast to org.apache.logging.log4j.core.LoggerContext
2. java.lang.StackOverflowError at org.apache.logging.log4j.util.StackLocator.getCallerClass
3. Slf4j: Found slf4j-api dependency but no providers were found
4. java.lang.AssertionError: unsafe symbol Level (child of package log4j) in runtime reflection universe
at scala.reflect.internal.Symbols$Symbol.<init>(Symbols.scala:224)
at scala.reflect.internal.Symbols$TypeSymbol.<init>(Symbols.scala:3070)
at scala.reflect.internal.Symbols$ClassSymbol.<init>(Symbols.scala:3261)
at scala.reflect.internal.Symbols$StubClassSymbol.<init>(Symbols.scala:3539)
这些问题一度让我认为是Spark scala版本升级导致的(当然也是我在其中排除了各种乱七八糟的log包导致的问题),以上问题在google中也能搜寻到,
但是最后反思了一下:还是对log sl4j logback理解的不够:
- slf4j 原理及使用原则,可以参考slf4j 原理及使用原则
这里面讲述了slf4j的一些原理以及关系 - 常用日志框架(Log4j,Slf4j,Logback)之间到底有啥区别 可以参考 常用日志框架(Log4j,Slf4j,Logback)之间到底有啥区别
这里面讲了logback与其中的关系 - Difference between slf4j-log4j12 and log4j-slf4j-impl,可以参考Difference between slf4j-log4j12 and log4j-slf4j-impl
这里讲了slf4j1.x slf4j2.x与Log4j 2.x Log4j 1.2的jar包的关系 - 秒懂log4j1与log4j2的区别,参考秒懂log4j1与log4j2的区别
这里最为关键:
这里说明可以用log4j-api 适配logback,再加上极客时间的讨论:slf4j的成功在于他的高屋建瓴,俯视一切。slf4j是日志门面(像:java的接口,没有提供任何实现),通过提供各种桥接器,适配各种日志框架(log4j1,logback等)。log4j1并没有这样的高度,于是log4j2就借鉴了slf4j的设计,log4f2有两部分组成:log4j-api、log4j-core。log4j-api和slf4j是相同的,都是日志门面,log4j-core是对log4j-api的实现,和log4j1、logback是相同的, 并且通过桥接器log4j-api还可以适配其他的日志系统(logback等)。这样log4j2就成了一个文堪比slf4,武可斗logback的双优生
Geek_58afe9 问: "其实,我们只是换成了 Log4j2 API,真正的日志记录还是走的 Logback 框架。没错,这就是 SLF4J 适配的一个好处。". Log4j2 和 LogBack 不是同质化产品吗, Log4j2 api 怎么会走到Logback? 作者回复: log4j2 API,并不是log4j 你在ch.qos.logback.classic.Logger.buildLoggingEventAndAppend设一个断点看一下整个过程就知道了,或者直接访问 https://github.com/JosephZhu1983/java-common-mistakes/blob/master/src/main/java/org/geekbang/time/commonmistakes/logging/placeholder/log4j2api_to_slf4j_to_logack.jpg 查看```
从以上我们知道 log4j2 不仅仅可以和slf4j适配,也可以和logback适配,这提供了另一条思路:不要只考虑slf4j和log4的爱恨情仇,也得考虑log4j和logback的亲密关系
解决
最终我们只留下了log4j2 (log4j-core + log4j-api) + logback (logback-classic + logback-core) ,其他的都排除掉,web端打包加编译没有任何问题,一切还是那么的美好(毕竟花了一天时间)
其他
- log4j1 如何平滑升级到log4j2呢