为什么 contextlib.redirect_stderr 上下文管理器未捕获异常输出?

Why is Exception output not captured by contextlib.redirect_stderr context manager?

在下面的代码中,我希望将异常输出和回溯写入 test.txt 文件,但那并没有发生。

import contextlib

with open("test.txt", "a") as f:
    with contextlib.redirect_stderr(f):
        raise Exception("Hello")

为什么它没有按预期工作以及如何正确地将异常输出重定向到文件?

因为重定向 sys.stderr 不会捕获 python 中的异常(如果捕获了就不好了,对吧?因为异常只应该被 try/except 块捕获。 ) 因此异常处理代码会中断您的 运行 代码(和重定向)。

证明:

import contextlib
from sys import stderr

with open("test.txt", "a") as f:
    with contextlib.redirect_stderr(f):
        sys.stderr.write("hello")
        sys.stderr.flush()

写入正确。

要将所有异常的输出重定向到文件,您有两种选择:在 try/except 块中全局捕获所有异常并自己编写,或者使用 shell 重定向发送 脚本的 stderr 到文件,类似

python my_script.py 2>errors.txt

如果您想自己做,请参阅 this question 获取回溯的讨论。

只是为了添加一个我想要的替代实现。

以下是将异常输出重定向到记录器。在我的例子中,记录器被设置为标准输出(默认)所以我使用 contextlib.redirect_stdout 将记录器的输出重定向到一个文件,但是你当然可以直接让记录器写入一个文件。

import logging
import contextlib
from typing import Iterator


@contextlib.contextmanager
def capture_exception(logger: logging.Logger) -> Iterator[None]:
    """
    Captures exceptions and redirects output to the logger
    >>> import logging
    >>> logger = logging.getLogger()
    >>> with capture_exception(logger=logger):
    >>>     raise Exception("This should be outputed to the logger")
    """
    try:
        # try/except block where exception is captured and logged
        try:
            yield None
        except Exception as e:
            logger.exception(e)
    finally:
        pass

用法:

logger = logging.getLogger()   # default should be stdout

with open("test.txt", "a") as f:
    with contextlib.redirect_stdout(f), capture_exception(logger):
        # ^-- multiple context managers in one `with` statement
        raise Exception("The output of this exception will appear in the log. Great success.")

日志中显示的异常输出:

The output of this exception will appear in the log. Great success.
Traceback (most recent call last):
  File "/tmp/ipykernel_9802/2529855525.py", line 52, in capture_exception
    yield None
  File "/tmp/ipykernel_9802/3344146337.py", line 9, in <module>
    raise Exception("The output of this exception will appear in the log. Great success.")
Exception: The output of this exception will appear in the log. Great success.