如何从Python Spark脚本登录

如何从Python Spark脚本登录,python,logging,apache-spark,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()开始。在这两种情况下,我

我有一个Python Spark程序,我使用
Spark submit
运行该程序。我想把日志记录语句放进去

logging.info("This is an informative message.")
logging.debug("This is a debug message.")
我想使用Spark使用的相同记录器,以便日志消息以相同的格式显示,并且级别由相同的配置文件控制。我该怎么做

我尝试将
logging
语句放入代码中,并从
logging.getLogger()
开始。在这两种情况下,我都能看到Spark的日志消息,但我看不到。我一直在看这张照片,但没能从那里弄清楚


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

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

logger = logging.getLogger('py4j')
logger.info("My test info statement")
它也可能是
'pyspark'
而不是
'py4j'

如果您在spark程序中使用的函数(并执行一些日志记录)与主函数定义在同一个模块中,则会出现一些序列化错误

本文对此进行了解释,并给出了同一个人的一个例子

我还在spark 1.3.1上测试了这个

编辑:

要将日志记录从
STDERR
更改为
STDOUT
,必须删除当前的
StreamHandler
,然后添加一个新的

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

sys.stdout

import sys # Put at top if not already there
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.DEBUG)
logger.addHandler(sh)

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

log4jLogger = sc._jvm.org.apache.log4j
LOGGER = log4jLogger.LogManager.getLogger(__name__)
LOGGER.info("pyspark script logger initialized")

在我的例子中,我很高兴将我的日志消息添加到workers stderr中,以及通常的spark日志消息

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

例如,以下灵感来源于,非常适合我:

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')
    ...
输出(加上上下文中的几行):


pyspark与javalog4j交互的关键是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")

我们需要从执行者而不是从驱动程序节点进行日志记录。因此,我们采取了以下措施:

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
  • 我们在所有节点上创建了一个
    /etc/rsyslog.d/spark.conf
    (使用Amazon Elastic Map Reduce
    的引导方法),以便核心节点将syslog
    local1`消息转发给主节点

  • 在主节点上,我们启用了UDP和TCP系统日志侦听器,并对其进行了设置,以便将所有
    local
    消息记录到
    /var/log/local1.log

  • 我们在map函数中创建了一个Python
    logging
    模块Syslog记录器

  • 现在我们可以使用
    logging.info()
    登录了

  • 我们发现的一件事是,同一个分区在多个执行器上同时被处理。显然,Spark在有额外资源的情况下总是这样做。这可以处理执行器神秘延迟或失败的情况

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

    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登录的最简单方法!

    您可以在一个类中实现
    logging.Handler
    接口,该类将日志消息转发到Spark下的log4j。然后使用
    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.")
    

    这里有一个更完整的例子:

    我是否必须将
    logger
    对象作为参数传递给所有使用它的组件?是否有某种方法可以全局设置它?只要您不执行线程或多处理,就应该能够在模块顶部设置它,并在任何地方使用它。只需更改
    日志即可。
    记录器。
    任何时候你要记录一些东西。谢谢。它是这样工作的。但消息总是传到stderr。我们如何才能直接到stdout或stderr?我更新了我的答案,为你解决这个问题。可能有一种方法可以更新现有的StreamHandler,我不确定,但以上是我知道如何做的。我很想否决投票这个答案是因为它不适用于我。通过pyspark源代码查看,pyspark从未配置py4j记录器,py4j使用java.utils.logging而不是spark使用的log4j记录器,因此我怀疑这是否可行。我认为这可能适用于主节点上的代码,但不适用于在t上运行的任何东西他是工人。我明白了问题:logger=logging.getLogger('py4j')TypeError:'JavaPackage'对象不可调用这绝对允许我像Spark那样进行日志记录(谢谢!)。除了从SparkContext获取日志外,还有其他方法获取此日志吗?我有一些日志必须在生成SparkContext之前打印created@marlieg在创建spark上下文之前,您无法访问spark日志记录。我在PySpark中尝试使用此想法时出错。我所做的是尝试将记录器存储为全局日志记录,但没有我的用例能够在foreach函数中对我的执行器进行日志调用(在foreach函数中没有spark上下文)。异常:您似乎试图从广播变量、操作或转换引用SparkContext。SparkContext只能在驱动程序上使用,不能在工作程序上运行的代码中使用。有关详细信息,请参阅SPARK-5063。“我可以这样做,但无法确定日志存储在哪里,有人能帮我吗?在getlogger函数中导入日志和导入系统有什么特殊原因吗?这在工作人员中不起作用…似乎只在驱动程序中起作用。您可能看不到您的日志记录。”
    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 = 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.")