任何应用程序的重要部分是我们并入其中的基础日志系统。 日志不仅用于调试和可追溯性,还用于商业智能。 在我们的应用程序中构建强大的日志记录系统可以用作对我们正在解决的业务问题的深刻见解。
Apache Spark中的Log4j
Spark使用log4j作为其自己的日志记录的标准库。 Spark内部发生的所有事情都会记录到Shell控制台和已配置的基础存储中。 Spark还为应用程序编写者提供了一个模板,因此我们可以使用相同的log4j库将所需的任何消息添加到Spark中现有的和就地实现的日志中。
配置Log4j
在SPARK_HOME / conf文件夹下,有log4j.properties.template文件,它是我们自己的日志记录系统的起点。
基于此文件,我们创建了log4j.properties文件并将其放置在同一目录下。
log4j.properties如下所示:
log4j.appender.myConsoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.myConsoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.myConsoleAppender.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
log4j.appender.RollingAppender=org.apache.log4j.DailyRollingFileAppender
log4j.appender.RollingAppender.File=/var/log/spark.log
log4j.appender.RollingAppender.DatePattern='.'yyyy-MM-dd
log4j.appender.RollingAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.RollingAppender.layout.ConversionPattern=[%p] %d %c %M - %m%n
log4j.appender.RollingAppenderU=org.apache.log4j.DailyRollingFileAppender
log4j.appender.RollingAppenderU.File=/var/log/sparkU.log
log4j.appender.RollingAppenderU.DatePattern='.'yyyy-MM-dd
log4j.appender.RollingAppenderU.layout=org.apache.log4j.PatternLayout
log4j.appender.RollingAppenderU.layout.ConversionPattern=[%p] %d %c %M - %m%n
# By default, everything goes to console and file
log4j.rootLogger=INFO, RollingAppender, myConsoleAppender
# My custom logging goes to another file
log4j.logger.myLogger=INFO, RollingAppenderU
# The noisier spark logs go to file only
log4j.logger.spark.storage=INFO, RollingAppender
log4j.additivity.spark.storage=false
log4j.logger.spark.scheduler=INFO, RollingAppender
log4j.additivity.spark.scheduler=false
log4j.logger.spark.CacheTracker=INFO, RollingAppender
log4j.additivity.spark.CacheTracker=false
log4j.logger.spark.CacheTrackerActor=INFO, RollingAppender
log4j.additivity.spark.CacheTrackerActor=false
log4j.logger.spark.MapOutputTrackerActor=INFO, RollingAppender
log4j.additivity.spark.MapOutputTrackerActor=false
log4j.logger.spark.MapOutputTracker=INFO, RollingAppender
log4j.additivty.spark.MapOutputTracker=false
基本上,我们想隐藏Spark生成的所有日志,因此我们不必在shell中处理它们。 我们将它们重定向以记录在文件系统中。 另一方面,我们希望将自己的日志记录在外壳程序中,并在单独的文件中记录日志,以免它们与Spark中的日志混淆。 从这里,我们将把Splunk指向我们自己的日志所在的文件,在这种情况下,该文件是/var/log/sparkU.log。
该( log4j.properties )文件在应用程序启动时由Spark拾取,因此除了将其放置在上述位置外,我们无需做任何其他事情。
编写自己的日志
现在我们已经配置了Spark管理日志所需的组件,我们只需要开始在应用程序中编写日志。
为了演示如何完成此操作,让我们编写一个小应用程序以帮助我们进行演示。
我们的应用程序:
object app {
def main(args: Array[String]) {
val log = LogManager.getRootLogger
log.setLevel(Level.WARN)
val conf = new SparkConf().setAppName("demo-app")
val sc = new SparkContext(conf)
log.warn("Hello demo")
val data = sc.parallelize(1 to 100000)
log.warn("I am done")
}
}
运行此Spark应用程序将证明我们的日志系统有效。 我们将能够看到Hello演示版和我完成了如何将消息记录在Shell和文件系统中,而Spark日志将仅转到文件系统。
到目前为止,一切似乎都很容易,但是有一个我们没有提到的问题。
org.apache.log4j.Logger类不可序列化 ,这意味着在对Spark API的某些部分进行操作时,不能在闭包内部使用它。
例如,如果我们在应用程序中执行以下操作:
val log = LogManager.getRootLogger
val data = sc.parallelize(1 to 100000)
data.map { value =>
log.info(value)
value.toString
}
在Spark上运行时,这将失败。 Spark抱怨该日志对象不可序列化,因此无法通过网络发送给Spark工作者。
这个问题实际上很容易解决。 让我们创建一个在执行大量日志记录的同时对我们的数据集执行某些操作的类。
class Mapper(n: Int) extends Serializable{
@transient lazy val log = org.apache.log4j.LogManager.getLogger("myLogger")
def doSomeMappingOnDataSetAndLogIt(rdd: RDD[Int]): RDD[String] =
rdd.map{ i =>
log.warn("mapping: " + i)
(i + n).toString
}
}
映射器接收到RDD [Int]并返回RDD [String] ,并且还记录其映射的值。 在这种情况下,请注意如何将日志对象标记为@transient ,这允许序列化系统忽略日志对象。 现在,正在将Mapper序列化并发送给每个工作程序,但是在工作程序中需要它时,正在解析日志对象,从而解决了我们的问题。
另一个解决方案是将日志对象包装到一个对象构造中,并在各处使用它。 我们宁愿在将要使用它的类中放置日志 ,但替代方法也有效。
至此,我们的整个应用程序如下所示:
import org.apache.log4j.{Level, LogManager, PropertyConfigurator}
import org.apache.spark._
import org.apache.spark.rdd.RDD
class Mapper(n: Int) extends Serializable{
@transient lazy val log = org.apache.log4j.LogManager.getLogger("myLogger")
def doSomeMappingOnDataSetAndLogIt(rdd: RDD[Int]): RDD[String] =
rdd.map{ i =>
log.warn("mapping: " + i)
(i + n).toString
}
}
object Mapper {
def apply(n: Int): Mapper = new Mapper(n)
}
object app {
def main(args: Array[String]) {
val log = LogManager.getRootLogger
log.setLevel(Level.WARN)
val conf = new SparkConf().setAppName("demo-app")
val sc = new SparkContext(conf)
log.warn("Hello demo")
val data = sc.parallelize(1 to 100000)
val mapper = Mapper(1)
val other = mapper.doSomeMappingOnDataSetAndLogIt(data)
other.collect()
log.warn("I am done")
}
}
结论
现在,我们的日志显示在外壳中,并且也存储在它们自己的文件中。 Spark日志从外壳中隐藏,并记录到其自己的文件中。 我们还解决了尝试登录其他工作程序时出现的序列化问题。
现在,我们可以像今天使用的其他非分布式系统和应用程序一样,基于自己的Spark日志构建更强大的BI系统。 商业智能对我们来说是非常重要的,拥有正确的见解总是很高兴。
翻译自: https://www.javacodegeeks.com/2016/03/log-apache-spark.html