Jupyter Notebook日志记录ValueError:已关闭文件上的I/O操作。

10

我正在尝试为一些Jupyter Notebook代码(运行Pyspark3)添加日志记录。

在Stack Overflow上查找后,我发现有些答案说使用basicConfig()无效,因为Notebook会启动自己的日志记录会话。有些变通的答案指出可以运行reload(logging)来解决这个问题。基于这个想法,我设置我的日志记录如下:

from importlib import reload  # Not needed in Python 2
import logging
reload(logging)
logging.basicConfig(
    format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
    level=logging.INFO,
    datefmt="%y/%m/%d %H:%M:%S",
)
logger = logging.getLogger(__name__)

然后我运行了一个信息语句:logger.info("this is a test"),但出现了 I/O 值错误?我不确定这是什么意思。

--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib64/python3.6/logging/__init__.py", line 994, in emit
    stream.write(msg)
  File "/tmp/2950371398694308674", line 534, in write
    super(UnicodeDecodingStringIO, self).write(s)
ValueError: I/O operation on closed file
Call stack:
  File "/tmp/2950371398694308674", line 700, in <module>
    sys.exit(main())
  File "/tmp/2950371398694308674", line 672, in main
    response = handler(content)
  File "/tmp/2950371398694308674", line 318, in execute_request
    result = node.execute()
  File "/tmp/2950371398694308674", line 229, in execute
    exec(code, global_dict)
  File "<stdin>", line 1, in <module>
Message: 'this is a test'
Arguments: ()

这与记录与stdout/stderr的交互有关,但我不确定如何解决。

2
我遇到了相同的错误。这是由于笔记本环境与内置的Python模块logging不兼容所致。我正在尝试捕获并忽略该错误,因为日志记录确实发生了。如果成功,我会发布代码。 - Marc Maxmeister
哦,太好了!请分享。 - Alex
我已经在这上面工作了一段时间,但没有找到解决方法。很难再现。如果我将有问题的代码复制到一个新的笔记本中,则可以正常运行,没有错误。 - Marc Maxmeister
有趣。这可能与我的环境有关吗?我正在一个EMR集群上运行(版本5.29.0)。 - Alex
对我来说,这只发生在Jupyter笔记本中,并且只有当我在其中有很多代码时才会出现。如果我将最小版本粘贴到新的笔记本中,我就不会出现错误。 - Marc Maxmeister
我一直遇到同样的问题,也找不到解决方法。但是我发现为什么最小版本不会出错:因为它在同一个单元格上运行。问题在于,在pyspark3内核上运行时,每次运行新单元格时,sys.stdout似乎会不断变化,随之而来的是流的变化。 - Monstah
1个回答

4

在我上面的评论之后,我想出了这个解决办法。

问题似乎是由于sys.stdout与spark不兼容,或者至少在jupyter中使用时不兼容。您可以通过创建一个新的(Pyspark3)notebook,并导入sys,然后在不同的单元格中打印sys.stdout来轻松验证此问题:它们将打印不同的对象(在我的情况下,有4个对象并且会循环切换它们,但我不能确定为什么是4个;也许这是特定于我的集群配置,但随着我更改执行器数量或每个执行器的核心数而没有改变)。

我的解决办法:

logger = logging.getLogger(__name__)
logger.handlers[0].stream.write = print

这可以成功是因为我知道我的记录器只有一个处理程序,它是sys.stdout。如果您的记录器有更多处理程序(比如一个stdout和一个文件),我还没有想出如何仅更改stdout(我无法比较if stream == sys.stdout,因为问题的根本意味着对象已经改变,除非在创建记录器的同一单元格中执行此操作),因此这个解决方法可能并不适用于所有人。
如果我有更好的解决方案,我会编辑答案,但现在我正在使用这种方法,而且它像魔术一样发挥作用。
编辑:一年后,我仍然以同样的方式进行操作。

嗯,我使用opencensus-ext-azure登录到Azure应用洞察,并且当我运行您的代码时,它会显示“AttributeError:'AzureLogHandler'对象没有'stream'属性”。此外,我在笔记本的每个单元格中检查sys.stdout,它始终是相同的,但我仍然收到“ValueError:在关闭的文件上进行I/O操作。” 您是否碰巧有其他解决方法? - Vitamin C
很抱歉,我不知道应用洞察或开放式普查是什么。问题和答案都与Pyspark Jupyter笔记本有关,这是你正在处理的吗? - Monstah
是的,我也在使用它们。只是我正在使用从库导入的自定义处理程序将日志记录到 Azure。问题是,“logger.handlers [0] .stream.write = print”会引发错误,这就是为什么我想知道您是否有其他解决方法的原因。 - Vitamin C
哦,明白了。抱歉,我不知道这个自定义处理程序。但是我通过尝试标准处理程序并找出问题所在来解决了它;你可以尝试在你的自定义处理程序上调用“dir(logger.handlers[0])”(或任何索引),看看里面有什么! - Monstah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接