大数据平台采用yarn client模式提交spark 任务,并且多个离线Spark作业共用一个Driver,好处便在于——节省提交任务的时间。但同时也加大了运维工作的难度,因为任务日志打印到同一个文件中。
为了区分开各个业务流程的日志,平台引入了log4j2 RoutingAppender,配置如下所示:
- "1.0" encoding="UTF-8"?>
- <Configuration status="info">
- <Appenders>
- <Console name="std" target="SYSTEM_OUT">
- <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t [%C:%L] - %m%n" />
- Console>
- <Routing name="myAppender">
-
- <Routes pattern="${ctx:logfile}">
- <Route>
- <File name="log-${ctx:logfile}" fileName="${ctx:logfile}">
- <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t [%C:%L] - %m%n" />
- File>
- Route>
- Routes>
- <IdlePurgePolicy timeToLive="5" timeUnit="minutes"/>
- Routing>
- Appenders>
-
- <Loggers>
- <Logger name="my" level="INFO" additivity="false">
- <AppenderRef ref="myAppender"/>
- Logger>
- <Root level="INFO">
- <AppenderRef ref="std"/>
- Root>
- Loggers>
- Configuration>
最近数据开发部门在使用大数据平台的二次开发算子时,反馈说平台提供的Logger对象打印不出日志,抱着好奇的心态,研究了一下平台使用的日志框架。其实平台提供的Logger对象打印在executor端打印不出日志很正常,因为上述的log4j2.xml文件并没有分发到executor端,更没有定义名称为my的Logger。那么,executor端的日志该如何打印、收集呢?
注:
本文基于华为fusioninsight平台。在fusioninsight平台客户端下,默认提供了executor端的log4j-executor.properties文件,其内容如下:
- log4j.logger.org.sparkproject.jetty = WARN
- log4j.appender.sparklog = org.apache.log4j.RollingFileAppender
- log4j.rootCategory = INFO,sparklog
- spark.executor.log.level = INFO
- log4j.appender.sparklog.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} | %-5p | [%t] | %m | %l%n
- log4j.appender.sparklog.Append = true
- log4j.appender.sparklog.layout = org.apache.log4j.PatternLayout
- log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper = INFO
- log4j.appender.sparklog.MaxBackupIndex = 10
- log4j.appender.sparklog.File = ${spark.yarn.app.container.log.dir}/stdout
- log4j.logger.org.sparkproject.jetty.util.component.AbstractLifeCycle = ERROR
- log4j.logger.com.huawei.hadoop.dynalogger.DynaLog4jWatcher = OFF
- log4j.appender.sparklog.MaxFileSize = 50MB
- log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter = INFO
那么,executor侧的日志,该如何打印呢?
scala.Console提供的println和java 中的System.out.println效果是一样的。样例代码形如:
- scala.Console.println(s"info zipName is : ${zipName}")
- scala.Console.err.println(s"error zipName is : ${zipName}")
这便是目前大数据平台中输出executor端日志的方式。
优势
日志打印到stdout中,可使用yarn logs命令汇聚日志。
简单,无需额外的类继承,不需要修改log4j配置。
缺点
没有日志级别,有些DEBUG用的日志去掉的话,到了生产环境下真出现问题时,难以定位问题;若不去掉,那就会导致yarn上日志过多,看起来较费劲。
在问题描述部分,我们已经知道driver的logger在executor端打印不了日志,是因为在log4j配置文件中没有相应的logger,于是尝试将driver端配置改造到log4j-executor.properties中。如下所示:
- log4j.rootCategory = INFO,sparklog,my
- log4j.appender.my = org.apache.log4j.RollingFileAppender
- log4j.appender.my.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} | %-5p | [%t] | %m | %l%n
- log4j.appender.my.Append = true
- log4j.appender.my.layout = org.apache.log4j.PatternLayout
- log4j.appender.my.MaxBackupIndex = 10
- log4j.appender.my.File = ${spark.yarn.app.container.log.dir}/stdout
- log4j.appender.my.MaxFileSize = 50MB
优势
有日志级别,定位问题时可以修改日志级别。
缺点:
虽然该logger是模仿sparklog来写的,但是在实际测试过程中发现${spark.yarn.app.container.log.dir}并没有完成“宏参”替换,导致appender my打印的日志直接放到了应用启动的目录,即spark CoarseGrainedExecutorBackend进程启动路径下。这带来的弊端就是使用yarn logs命令去汇聚yarn上日志时,汇聚不了,后期定位问题不方便。(当container kill或exit时,日志文件也就被删除了)
这是Spark内部打印日志的方式,使用方式形如:
- class Operator(@transient bean: Bean) extends Serializable with Logging
- {
- dataFrame.foreachPartition(partition => {
- partition.foreach {
- logInfo(s"info org.apache.spark.internal.Logging print content")
- logError(s"error org.apache.spark.internal.Logging print content")
- }
- }
- )
- }
优势
定位问题时可以修改日志级别
可以通过yarn logs命令进行汇聚日志
缺点
需要额外继承org.apache.spark.internal.Logging类
扩展:
Spark - Logging 具体用法参考:Spark - Logging 简单使用
1、推荐使用继承org.apache.spark.internal.Logging类的方式,去输出executor端日志。
2、log4j2提供的RoutingAppender,可以实现每个业务日志输出到不同的文件中,方便问题定位。