如何从我的Python Spark脚本登录

时间:2014-08-20 14:37:07

标签: python logging apache-spark

我有一个Python Spark程序,我使用spark-submit运行。我想把日志语句放在其中。

logging.info("This is an informative message.")
logging.debug("This is a debug message.")

我想使用Spark正在使用的相同记录器,以便日志消息以相同的格式出现,并且级别由相同的配置文件控制。我该怎么做?

我已尝试将logging语句放在代码中,并以logging.getLogger()开头。在这两种情况下,我都看到Spark的日志消息,但不是我的。我一直在关注Python logging documentation,但无法从那里弄明白。

不确定这是否是提交给Spark的脚本特有的,或者我不了解日志记录的工作原理。

7 个答案:

答案 0 :(得分:37)

您可以从SparkContext对象获取记录器:

        foreach(DataContainer dataContainer in data) {
            foreach (int intValue in dataContainer.MyValue.IntegerValues) {
                Console.WriteLine(intValue);
            }
        }

答案 1 :(得分:14)

您需要获取spark本身的记录器,默认情况下getLogger()将为您自己的模块返回记录器。尝试类似:

logger = logging.getLogger('py4j')
logger.info("My test info statement")

也可能是'pyspark'而不是'py4j'

如果您在spark程序中使用的函数(以及执行某些日志记录)在与main函数相同的模块中定义,则会产生一些序列化错误。

这解释为here,同一个人的例子给出了here

我也在spark 1.3.1

上测试了这个

编辑:

要将记录从STDERR更改为STDOUT,您必须删除当前StreamHandler并添加新记录。

找到现有的流处理程序(完成后可以删除此行)

print(logger.handlers)
# will look like [<logging.StreamHandler object at 0x7fd8f4b00208>]

可能只有一个,但如果没有,你将不得不更新位置。

logger.removeHandler(logger.handlers[0])

sys.stdout

添加新处理程序
import sys # Put at top if not already there
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.DEBUG)
logger.addHandler(sh)

答案 2 :(得分:3)

交互pyspark和java log4j的关键是jvm。 下面是python代码,conf缺少url,但这是关于日志记录。

from pyspark.conf import SparkConf
from pyspark.sql import SparkSession

my_jars = os.environ.get("SPARK_HOME")
myconf = SparkConf()
myconf.setMaster("local").setAppName("DB2_Test")
myconf.set("spark.jars","%s/jars/log4j-1.2.17.jar" % my_jars)
spark = SparkSession\
 .builder\
 .appName("DB2_Test")\
 .config(conf = myconf) \
 .getOrCreate()


Logger= spark._jvm.org.apache.log4j.Logger
mylogger = Logger.getLogger(__name__)
mylogger.error("some error trace")
mylogger.info("some info trace")

答案 3 :(得分:3)

我们需要从执行者登录,而不是从驱动程序节点登录。因此,我们执行了以下操作:

  1. 我们在所有节点上创建了/etc/rsyslog.d/spark.conf(通过将Bootstrap方法与Amazon Elastic Map Reduce so that the Core nodes forwarded syslog local1`消息一起发送到主节点。

  2. 在主节点上,我们启用了UDP和TCP syslog侦听器,并将其设置为使所有local消息都记录到/var/log/local1.log

  3. 我们在地图函数中创建了一个Python logging模块Syslog记录器。

  4. 现在我们可以使用logging.info()登录。 ...

我们发现的一件事是同一分区正在多个执行器上同时处理。显然,只要有额外的资源,Spark就会一直这样做。当执行者被神秘地延迟或失败时,这可以处理这种情况。

登录map函数使我们了解了很多Spark的工作原理。

答案 4 :(得分:1)

在我的情况下,我很高兴将我的日志消息添加到worker stderr,以及通常的spark日志消息。

如果这符合您的需求,那么诀窍是将特定的Python记录器重定向到stderr

例如,受this answer启发的以下内容适用于我:

def getlogger(name, level=logging.INFO):
    import logging
    import sys

    logger = logging.getLogger(name)
    logger.setLevel(level)
    if logger.handlers:
        # or else, as I found out, we keep adding handlers and duplicate messages
        pass
    else:
        ch = logging.StreamHandler(sys.stderr)
        ch.setLevel(level)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        logger.addHandler(ch)
    return logger

用法:

def tst_log():
    logger = getlogger('my-worker')
    logger.debug('a')
    logger.info('b')
    logger.warning('c')
    logger.error('d')
    logger.critical('e')
    ...

输出(加上上下文的几条线):

17/05/03 03:25:32 INFO MemoryStore: Block broadcast_24 stored as values in memory (estimated size 5.8 KB, free 319.2 MB)
2017-05-03 03:25:32,849 - my-worker - INFO - b
2017-05-03 03:25:32,849 - my-worker - WARNING - c
2017-05-03 03:25:32,849 - my-worker - ERROR - d
2017-05-03 03:25:32,849 - my-worker - CRITICAL - e
17/05/03 03:25:32 INFO PythonRunner: Times: total = 2, boot = -40969, init = 40971, finish = 0
17/05/03 03:25:32 INFO Executor: Finished task 7.0 in stage 20.0 (TID 213). 2109 bytes result sent to driver

答案 5 :(得分:1)

import logging

# Logger

logging.basicConfig(format='%(asctime)s %(filename)s %(funcName)s %(lineno)d %(message)s')
logger = logging.getLogger('driver_logger')
logger.setLevel(logging.DEBUG)

从pyspark登录的最简单方法!

答案 6 :(得分:0)

您可以在 Spark 下将日志消息转发到 log4j 的类中实现 logging.Handler 接口。然后使用 logging.root.addHandler()(以及可选的 logging.root.removeHandler())安装该处理程序。

处理程序应该有一个类似如下的方法:

def emit(self, record):
    """Forward a log message for log4j."""
    Logger = self.spark_session._jvm.org.apache.log4j.Logger
    logger = Logger.getLogger(record.name)
    if record.levelno >= logging.CRITICAL:
        # Fatal and critical seem about the same.
        logger.fatal(record.getMessage())
    elif record.levelno >= logging.ERROR:
        logger.error(record.getMessage())
    elif record.levelno >= logging.WARNING:
        logger.warn(record.getMessage())
    elif record.levelno >= logging.INFO:
        logger.info(record.getMessage())
    elif record.levelno >= logging.DEBUG:
        logger.debug(record.getMessage())
    else:
        pass

在初始化 Spark 会话后应立即安装处理程序:

spark = SparkSession.builder.appName("Logging Example").getOrCreate()
handler = CustomHandler(spark_session)
# Replace the default handlers with the log4j forwarder.
root_handlers = logging.root.handlers[:]
for h in self.root_handlers:
    logging.root.removeHandler(h)
logging.root.addHandler(handler)

# Now you can log stuff.
logging.debug("Installed log4j log handler.")

这里有一个更完整的例子:https://gist.github.com/thsutton/65f0ec3cf132495ef91dc22b9bc38aec