
PySpark 中通过 Log4j 获取的自定义 Logger(如 'Example Processor')未输出 INFO 级别日志,根本原因在于 Log4j 根记录器(root logger)的级别限制了子记录器的实际生效级别;即使显式设置了子 Logger 级别为 INFO,若 root logger 级别为 WARN,则 INFO 及以下日志仍被静默丢弃。
pyspark 中通过 log4j 获取的自定义 logger(如 `'example processor'`)未输出 info 级别日志,根本原因在于 **log4j 根记录器(root logger)的级别限制了子记录器的实际生效级别**;即使显式设置了子 logger 级别为 info,若 root logger 级别为 warn,则 info 及以下日志仍被静默丢弃。
在 PySpark 应用中,日志系统基于 JVM 层的 Log4j(Spark 3.0+ 默认使用 Log4j 2,但 PySpark Python API 仍主要通过 org.apache.log4j 兼容接口操作)。关键要理解 Log4j 的双级过滤机制:
- Logger 级别(如 self.log.setLevel(Level.INFO)):决定该 Logger 是否接受 某条日志(即“准入门槛”);
- Appender/Root Logger 级别(如 LogManager.getRootLogger().setLevel(Level.WARN)):决定日志 是否最终输出(即“出口闸门”)。
只有当一条日志同时满足 Logger 级别 ≥ 当前日志级别 且 root logger 级别 ≤ 当前日志级别 时,它才会被打印。这就是为什么你设置 self.log.setLevel(Level.INFO) 后,info() 仍不输出——因为 Spark 启动时默认将 root logger 设为 WARN(见控制台提示 "Setting default log level to 'WARN'"),而 INFO
✅ 正确做法是:同步提升 root logger 级别,而非仅设置子 Logger。以下是推荐的完整配置方案:
from pyspark.sql import SparkSession
self.spark = SparkSession.builder \
.master("local[1]") \
.appName("DemoProcessor") \
.getOrCreate()
# 获取 JVM Log4j 接口
log4j = self.spark.sparkContext._jvm.org.apache.log4j
# ✅ 步骤1:设置 root logger 级别为 INFO(关键!)
root_logger = log4j.LogManager.getRootLogger()
root_logger.setLevel(log4j.Level.INFO)
# ✅ 步骤2:创建并配置自定义 Logger
self.log = log4j.LogManager.getLogger("Example Processor")
self.log.setLevel(log4j.Level.INFO) # 此步可选,因继承自 root,但建议显式声明
# 测试日志(INFO 及以上均可见)
self.log.info("Info Message!") # ✅ 现在可见
self.log.warn("Warn Message!") # ✅
self.log.error("Error Message!") # ✅
self.log.debug("Debug Message!") # ❌ 仍不可见(DEBUG < INFO)⚠️ 注意事项:
- 不要仅依赖 sc.setLogLevel("INFO"):该方法仅控制 Spark 内部日志(如 org.apache.spark 包下日志),不影响用户自定义 Logger(如 "Example Processor"),因其属于独立命名空间。
- 避免 setLevel(Level.DEBUG) 用于生产环境:DEBUG 级别会产生海量日志,显著影响性能与磁盘 I/O;开发调试时启用,上线前务必回调至 INFO 或 WARN。
- Log4j 2 用户注意:若集群启用 Log4j 2(通过 spark.jars 指定 log4j-api/log4j-core),需改用 org.apache.logging.log4j 包,并调用 LogManager.getLogger(...) + Configurator.setLevel(...),但 PySpark 3.x 默认仍兼容 Log4j 1.x 接口。
- 线程安全:Log4j Logger 是线程安全的,可在多线程任务(如 mapPartitions)中安全复用 self.log。
? 总结:PySpark 自定义日志生效的前提是 “子 Logger 级别 ≥ 日志级别” 且 “root Logger 级别 ≤ 日志级别”。解决 INFO 不输出问题,核心是调用 LogManager.getRootLogger().setLevel(Level.INFO) 打开根闸门。配置完成后,即可精准控制业务日志粒度,兼顾可观测性与运行效率。










