将日志记录中的应用程序日志与log4j中的Spark日志分开

时间:2017-02-09 00:22:02

标签: scala maven logging apache-spark jar

我有一个使用Spark的Scala Maven项目,我正在尝试使用Logback实现日志记录。我正在将我的应用程序编译到jar,并部署到安装了Spark发行版的EC2实例。 我的pom.xml包含Spark和Logback的依赖项,如下所示:

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.7</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_${scala.binary.version}</artifactId>
            <version>${spark.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

提交我的Spark应用程序时,我在命令行上打印出slf4j绑定。如果我使用java执行jar代码,则绑定是Logback。但是,如果我使用Spark(即spark-submit),则绑定到log4j。

  val logger: Logger = LoggerFactory.getLogger(this.getClass)
  val sc: SparkContext = new SparkContext()
  val rdd = sc.textFile("myFile.txt")

  val slb: StaticLoggerBinder = StaticLoggerBinder.getSingleton
  System.out.println("Logger Instance: " + slb.getLoggerFactory)
  System.out.println("Logger Class Type: " + slb.getLoggerFactoryClassStr)

产量

Logger Instance: org.slf4j.impl.Log4jLoggerFactory@a64e035
Logger Class Type: org.slf4j.impl.Log4jLoggerFactory

我知道log4j-1.2.17.jarslf4j-log4j12-1.7.16.jar都在/ usr / local / spark / jars中,而且尽管在我的pom.xml中排除了,但Spark最有可能引用这些jar,因为如果我删除它们我在spark-submit的运行时给了一个ClassNotFoundException。

我的问题是:是否有办法使用Logback在我的应用程序中实现本机日志记录,同时保留Spark的内部日志记录功能。理想情况下,我想将我的Logback应用程序日志写入文件,并允许Spark日志仍显示在STDOUT。

5 个答案:

答案 0 :(得分:6)

我遇到了一个非常类似的问题。

我们的版本与您的版本类似(但我们使用了sbt)并在此处详细说明:https://stackoverflow.com/a/45479379/1549135

在本地运行此解决方案,然后spark-submit忽略所有排除项和新的日志记录框架(logback),因为spark的类路径优先于部署的jar。由于它包含log4j 1.2.xx,因此只需加载它并忽略我们的设置。

解决方案

我使用过多种来源。但引用Spark 1.6.1 docs(也适用于Spark latest / 2.2.0):

<强> spark.driver.extraClassPath

  

要添加到驱动程序类路径的额外类路径条目。   注意:在客户端模式下,不能直接在应用程序中通过SparkConf设置此配置,因为驱动程序JVM已在此时启动。相反,请通过--driver-class-path命令行选项或默认属性文件中设置它。

<强> spark.executor.extraClassPath

  

要添加到执行程序的类路径的额外类路径条目。这主要是为了向后兼容旧版本的Spark。用户通常不需要设置此选项。

这里没有写的是extraClassPath 优先于默认Spark的类路径!

所以现在解决方案应该非常明显。

1。下载这些罐子:

- log4j-over-slf4j-1.7.25.jar
- logback-classic-1.2.3.jar
- logback-core-1.2.3.jar

2。运行spark-submit

libs="/absolute/path/to/libs/*"

spark-submit \
  ...
  --master yarn \
  --conf "spark.driver.extraClassPath=$libs" \
  --conf "spark.executor.extraClassPath=$libs" \
  ...
  /my/application/application-fat.jar \
  param1 param2

我还不确定你是否可以将这些罐放在HDFS上。我们将它们放在应用程序jar旁边。

userClassPathFirst

奇怪的是,使用Spark 1.6.1我还在docs中找到了这个选项:

spark.driver.userClassPathFirst spark.executor.userClassPathFirst

  

(实验)在驱动程序中加载类时,是否优先使用用户添加的jar优先于Spark自己的jar。此功能可用于缓解Spark的依赖项和用户依赖项之间的冲突。它目前是一个实验性功能。这仅在群集模式下使用。

但只需设置:

--conf "spark.driver.userClassPathFirst=true" \
--conf "spark.executor.userClassPathFirst=true" \

对我不起作用。所以我很高兴使用extraClassPath

干杯!

正在加载logback.xml

如果您在向Spark添加logback.xml时遇到任何问题,我的问题可能会帮到您: Pass system property to spark-submit and read file from classpath or custom path

答案 1 :(得分:3)

我遇到了同样的问题:我试图使用logback配置文件。我尝试了许多排列,但我没有让它发挥作用。

我使用这个SBT依赖项通过grizzled-slf4j访问logback:

"org.clapper" %% "grizzled-slf4j" % "1.3.0",

一旦我添加了log4j配置文件:

src/main/resources/log4j.properties/log4j.properties files.

我的日志工作正常。

答案 2 :(得分:0)

经过多次努力,我找到了另一种解决方案:库阴影。在我使用着色org.slf4j之后,我的应用程序日志与spark日志分开。此外,我的应用程序jar中的logback.xml很荣幸。

https://github.com/pjreddie/darknet/blob/master/python/darknet.py,在这种情况下,它归结为:

assemblyShadeRules in assembly += ShadeRule.rename(s"org.slf4j.**" -> "your_favourite_prefix.@0").inAll

在您的build.sbt设置中。

附注 :如果您不确定是否实际出现了阴影,请在某些存档浏览器中打开您的jar并检查目录结构是否反映了阴影,在这种情况下,您的jar应包含路径/your_favourite_prefix/org/slf4j,但不应包含/org/slf4j

答案 3 :(得分:0)

我将logback和log4j-to-slf4j以及其他依赖项和src / main / resources / logback.xml打包在一个胖子罐中。

当我运行“火花提交”时

--conf "spark.driver.userClassPathFirst=true" \
--conf "spark.executor.userClassPathFirst=true"

所有日志记录都是通过logback处理的。

答案 4 :(得分:0)

我必须修改Atais提出的解决方案才能使其在集群模式下工作。 这对我有用:

libs="/absolute/path/to/libs/*"

spark-submit \
--master yarn \
--deploy-mode cluster \
... \
--jars $libs \
--conf spark.driver.extraClassPath=log4j-over-slf4j-1.7.25.jar:logback-classic-1.2.3.jar:logback-core-1.2.3.jar:logstash-logback-encoder-6.4.jar \
--conf spark.executor.extraClassPath=log4j-over-slf4j-1.7.25.jar:logback-classic-1.2.3.jar:logback-core-1.2.3.jar:logstash-logback-encoder-6.4.jar \
/my/application/application-fat.jar \
param1 param2

根本原因是jar并非对所有节点都可用,并且必须显式地可用(即使使用--jars提交之后)。

更新:进一步完善解决方案。您还可以将罐子作为URL列表传递,即--jars url1,url2,url3。这些jar仍必须添加到类路径中,以优先于log4j。