ChatGPT解决这个技术问题 Extra ChatGPT

如何将 sys.stdout 复制到日志文件?

编辑:因为似乎没有解决方案,或者我正在做一些非常不标准的事情以至于没人知道 - 我会修改我的问题来问:当 python 应用程序正在制作时,完成日志记录的最佳方法是什么?很多系统调用?

我的应用程序有两种模式。在交互模式下,我希望所有输出到屏幕以及日志文件,包括来自任何系统调用的输出。在守护程序模式下,所有输出都进入日志。使用 os.dup2() 的守护程序模式效果很好。在不修改每个系统调用的情况下,我找不到一种将所有输出“发送”到交互模式下的日志的方法。

换句话说,我想要命令行“tee”的功能,用于 python 应用程序生成的任何输出,包括系统调用输出。

澄清:

为了重定向所有输出,我做了这样的事情,效果很好:

# open our log file
so = se = open("%s.log" % self.name, 'w', 0)

# re-open stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# redirect stdout and stderr to the log file opened above
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

这样做的好处是它不需要来自其余代码的特殊打印调用。该代码还运行一些 shell 命令,因此不必单独处理它们的每个输出也很好。

简单地说,我想做同样的事情,除了复制而不是重定向。

起初,我认为简单地反转 dup2 应该可以工作。为什么不呢?这是我的测试:

import os, sys

### my broken solution:
so = se = open("a.log", 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

os.dup2(sys.stdout.fileno(), so.fileno())
os.dup2(sys.stderr.fileno(), se.fileno())
###

print("foo bar")

os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

文件“a.log”应该与屏幕上显示的相同。

如果您查看手册页 (manpagez.com/man/2/dup2),则 dup2 的第二个参数始终是关闭的(如果它已经打开)。因此,在您的“损坏的解决方案”中,它正在关闭 so 和 se,然后将它们的 filenos 重新分配给 sys.stdout。
回复:您的编辑:这并不少见,我做过几次类似的事情(在其他语言中)。虽然 Unix 将允许同一个文件句柄有多个“别名”,但它不会“拆分”一个文件句柄(将其复制到其他多个文件句柄)。所以你必须自己实现“tee”(或者只使用“tee”,看看我的粗略回答)。
我认为 JohnT 的答案比实际接受的要好。您可能想要更改接受的答案。
“我正在做一些非常不标准的事情” - 你真的是,人们只是将他们的日志发送到 stderr 并从命令行处理。

A
Andrej Debenjak

我之前遇到过同样的问题,发现这个片段非常有用:

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self
    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()
    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)
    def flush(self):
        self.file.flush()

来自:http://mail.python.org/pipermail/python-list/2007-May/438106.html


+1 用于在内部处理 sys.stdout 重新分配,以便您可以通过删除 Tee 对象来结束日志记录
我会为此添加一个冲洗。例如:'self.file.flush()'
我不同意日志记录模块。非常适合一些摆弄。日志记录太大了。
那不管用。 __del__ 直到执行结束才被调用。请参阅stackoverflow.com/questions/6104535/…
此解决方案不会记录除 print()sys.stdout.write() 之外的其他调用生成的输出。由 C 代码或用 Python 包装的 Fortran 生成的输出不会被重定向到文件,因为它们直接写入 stdout fd (1),而不是 IOTextwrapper sys.stdout。 @Jacob Gabrielson 的方法虽然可以解决这个问题,但请查看他的回答评论以编辑他的提案。
K
Kenan Banks

print 语句将调用您分配给 sys.stdout 的任何对象的 write() 方法。

我会开设一个小班,一次写到两个地方……

import sys

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("log.dat", "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

sys.stdout = Logger()

现在 print 语句将在屏幕上回显并附加到您的日志文件中:

# prints "1 2" to <stdout> AND log.dat
print "%d %d" % (1,2)

这显然是快速而肮脏的。一些注意事项:

您可能应该参数化日志文件名。

如果您在程序期间不进行日志记录,您可能应该将 sys.stdout 恢复为

您可能希望能够一次写入多个日志文件,或处理不同的日志级别等。

这些都很简单,我很乐意将它们作为练习留给读者。这里的关键见解是 print 只是调用分配给 sys.stdout 的“类文件对象”。


正是我要发布的内容,差不多。 +1 当您解决 write 没有 self 参数的问题时。此外,将要写入的文件传入传递会是更好的设计。地狱,传入 stdout 也可能是更好的设计。
@Devin,是的,这又快又脏,我会为可能的早期改进做一些笔记。
我太早选择了这个答案。它适用于“打印”,但不适用于外部命令输出。
Logger 类还应该定义一个 flush() 方法,例如“def flush(): self.terminal.flush(); self.log.flush()”
你说The print statement will call the write() method of any object you assign to sys.stdout。其他不使用 print 向标准输出发送数据的函数呢?例如,如果我使用 subprocess.call 创建一个进程,它的输出会发送到控制台而不是 log.dat 文件......有没有办法解决这个问题?
S
Serge Stroobandt

您真正想要的是标准库中的 logging 模块。创建一个记录器并附加两个处理程序,一个将写入文件,另一个写入 stdout 或 stderr。

有关详细信息,请参阅 Logging to multiple destinations


日志记录模块不会将异常和其他重要输出记录到标准输出,这在分析构建服务器上的日志时非常有用(例如)。
logging 模块不会重定向来自系统调用(例如 os.write(1, b'stdout'))的输出
J
Jacob Gabrielson

由于您乐于从代码中生成外部进程,因此您可以使用 tee 本身。我不知道有任何 Unix 系统调用完全符合 tee 的功能。

# Note this version was written circa Python 2.6, see below for
# an updated 3.3+-compatible version.
import subprocess, os, sys

# Unbuffer output (this ensures the output is in the correct order)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

print "\nstdout"
print >>sys.stderr, "stderr"
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

您还可以使用 multiprocessing 包模拟 tee(如果您使用的是 Python 2.5 或更早版本,则使用 processing)。

更新

这是一个 Python 3.3+ 兼容的版本:

import subprocess, os, sys

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
# Cause tee's stdin to get a copy of our stdin/stdout (as well as that
# of any child processes we spawn)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

# The flush flag is needed to guarantee these lines are written before
# the two spawned /bin/ls processes emit any output
print("\nstdout", flush=True)
print("stderr", file=sys.stderr, flush=True)

# These child processes' stdin/stdout are 
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

好吧,这个答案有效,所以我会接受它。不过,这让我觉得很脏。
我刚刚发布了 tee 的纯 python 实现(兼容 py2/3),它可以在任何平台上运行,也可以用于不同的日志记录配置。 stackoverflow.com/questions/616645/…
如果 Python 在我的一台机器上运行而解决方案没有运行,那么这不是 Pythonic 解决方案。因此被否决。
根据 this post,行 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) 自 python 3.3 起不再有效(参见 PEP 3116)
好的,我要补充:(... , preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN) 一旦用户点击“Ctrl+C”,“tee”进程首先被杀死,关闭 stdout\stderr FD,然后在主线程中没有打印输出。这解决了问题:)
s
shx2

这是另一种解决方案,它比其他解决方案更通用——它支持将输出(写入 sys.stdout)拆分为任意数量的类似文件的对象。不要求包含 __stdout__ 本身。

import sys

class multifile(object):
    def __init__(self, files):
        self._files = files
    def __getattr__(self, attr, *args):
        return self._wrap(attr, *args)
    def _wrap(self, attr, *args):
        def g(*a, **kw):
            for f in self._files:
                res = getattr(f, attr, *args)(*a, **kw)
            return res
        return g

# for a tee-like behavior, use like this:
sys.stdout = multifile([ sys.stdout, open('myfile.txt', 'w') ])

# all these forms work:
print 'abc'
print >>sys.stdout, 'line2'
sys.stdout.write('line3\n')

注意:这是一个概念验证。这里的实现并不完整,因为它只包装了类文件对象(例如 write)的方法,省略了成员/属性/setattr 等。但是,它可能已经足够大多数人目前的情况。

除了通用性之外,我喜欢它的地方在于它是干净的,因为它不会直接调用 writeflushos.dup2 等。


我会初始化 *files 而不是文件,否则,是的,这个。没有其他解决方案在不尝试解决其他问题的情况下隔离“三通”功能。如果你想在你输出的所有东西上加上一个前缀,你可以把这个类包装在一个前缀编写器类中。 (如果你只想在一个流上加上前缀,你可以包装一个流并将它交给这个类。)这个还有一个优点是 multifile([]) 创建一个忽略所有内容的文件(比如 open('/dev /无效的'))。
为什么这里有 _wrap?您不能将其中的代码复制到 __getattr__ 中并且它的工作方式相同吗?
@Ben 实际上 multifile([]) 会创建一个文件,只要您调用其中一个方法,就会引发 UnboundLocalError 。 (res 未赋值就返回)
由于它是多文件,您也可以添加 sys.stderr。它也适用于 Windows。唯一的缺点:在异常情况下,打印 Traceback 在 python 2.x 中丢失并且无法记录。
C
Community

我知道这个问题已被反复回答,但为此我从 John T's 答案中获取了主要答案并对其进行了修改,使其包含建议的刷新并遵循其链接的修订版本。我还添加了 cladmi's 答案中提到的进入和退出,以便与 with 语句一起使用。此外,documentation 提到使用 os.fsync() 刷新文件,所以我也添加了它。我不知道您是否真的需要它,但它就在那里。

import sys, os

class Logger(object):
    "Lumberjack class - duplicates sys.stdout to a log file and it's okay"
    #source: https://stackoverflow.com/q/616645
    def __init__(self, filename="Red.Wood", mode="a", buff=0):
        self.stdout = sys.stdout
        self.file = open(filename, mode, buff)
        sys.stdout = self

    def __del__(self):
        self.close()

    def __enter__(self):
        pass

    def __exit__(self, *args):
        self.close()

    def write(self, message):
        self.stdout.write(message)
        self.file.write(message)

    def flush(self):
        self.stdout.flush()
        self.file.flush()
        os.fsync(self.file.fileno())

    def close(self):
        if self.stdout != None:
            sys.stdout = self.stdout
            self.stdout = None

        if self.file != None:
            self.file.close()
            self.file = None

然后你可以使用它

with Logger('My_best_girlie_by_my.side'):
    print("we'd sing sing sing")

或者

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()

许多 Thnaks @Status 你解决了我的问题(stackoverflow.com/questions/39143417/…)。我将链接到您的解决方案。
@MohammadElNesr 我刚刚意识到代码与 with 语句一起使用时存在问题。我已经修复了它,它现在在 with 块的末尾正确关闭。
这对我很有用,只需将模式更改为 mode="ab" 并在 write 函数中 self.file.write(message.encode("utf-8"))
b
blokeley

如其他地方所述,也许最好的解决方案是直接使用日志记录模块:

import logging

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')
logging.info('this should to write to the log file')

但是,在某些(罕见的)情况下,您确实想要重定向标准输出。当我扩展使用 print 的 django 的 runserver 命令时,我遇到了这种情况:我不想破解 django 源,但需要打印语句才能进入文件。

这是一种使用 logging 模块将 stdout 和 stderr 重定向到 shell 之外的方法:

import logging, sys

class LogFile(object):
    """File-like object to log text using the `logging` module."""

    def __init__(self, name=None):
        self.logger = logging.getLogger(name)

    def write(self, msg, level=logging.INFO):
        self.logger.log(level, msg)

    def flush(self):
        for handler in self.logger.handlers:
            handler.flush()

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')

# Redirect stdout and stderr
sys.stdout = LogFile('stdout')
sys.stderr = LogFile('stderr')

print 'this should to write to the log file'

如果您确实不能直接使用日志记录模块,则应仅使用此 LogFile 实现。


如其他地方所述,使用这种解决方案,您不会看到在这种情况下最重要的异常和其他问题。
S
Solomon Ucko

完成 John T 的回答:https://stackoverflow.com/a/616686/395687

我添加了 __enter____exit__ 方法以将其用作具有 with 关键字的上下文管理器,这给出了此代码

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self

    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()

    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)

    def __enter__(self):
        pass

    def __exit__(self, _type, _value, _traceback):
        pass

然后它可以用作

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')

我会将 __del__ 功能移到 __exit__
事实上,我认为使用 __del__ 是个坏主意。应将其移至在 __exit__ 中调用的“关闭”函数。
s
sorin

我用 Python 编写了一个 tee() 实现,它应该适用于大多数情况,它也适用于 Windows。

https://github.com/pycontribs/tendo

此外,如果需要,您可以将它与 Python 中的 logging 模块结合使用。


哇,你的包太棒了,特别是如果你知道 Windows 控制台文化有多么繁琐但没有放弃让它工作!
A
Atlas1j

(啊,只需重新阅读您的问题,发现这并不完全适用。)

这是一个使用 python logging module 的示例程序。自 2.3 以来,此日志记录模块已存在于所有版本中。在此示例中,日志记录可通过命令行选项进行配置。

在相当模式下,它只会记录到文件,在正常模式下,它会同时记录到文件和控制台。

import os
import sys
import logging
from optparse import OptionParser

def initialize_logging(options):
    """ Log information based upon users options"""

    logger = logging.getLogger('project')
    formatter = logging.Formatter('%(asctime)s %(levelname)s\t%(message)s')
    level = logging.__dict__.get(options.loglevel.upper(),logging.DEBUG)
    logger.setLevel(level)

    # Output logging information to screen
    if not options.quiet:
        hdlr = logging.StreamHandler(sys.stderr)
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)

    # Output logging information to file
    logfile = os.path.join(options.logdir, "project.log")
    if options.clean and os.path.isfile(logfile):
        os.remove(logfile)
    hdlr2 = logging.FileHandler(logfile)
    hdlr2.setFormatter(formatter)
    logger.addHandler(hdlr2)

    return logger

def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    # Setup command line options
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-l", "--logdir", dest="logdir", default=".", help="log DIRECTORY (default ./)")
    parser.add_option("-v", "--loglevel", dest="loglevel", default="debug", help="logging level (debug, info, error)")
    parser.add_option("-q", "--quiet", action="store_true", dest="quiet", help="do not log to console")
    parser.add_option("-c", "--clean", dest="clean", action="store_true", default=False, help="remove old log file")

    # Process command line options
    (options, args) = parser.parse_args(argv)

    # Setup logger format and output locations
    logger = initialize_logging(options)

    # Examples
    logger.error("This is an error message.")
    logger.info("This is an info message.")
    logger.debug("This is a debug message.")

if __name__ == "__main__":
    sys.exit(main())

好答案。我看到了一些将日志记录复制到控制台的非常复杂的方法,但是使用 stderr 制作 StreamHandler 是我一直在寻找的答案:)
代码很好,它没有回答问题 - 这会将日志输出到文件和标准错误中,最初的问题是要求将标准错误复制到日志文件中。
D
Denis Barmenkov

使用日志记录模块的另一种解决方案:

import logging
import sys

log = logging.getLogger('stdxxx')

class StreamLogger(object):

    def __init__(self, stream, prefix=''):
        self.stream = stream
        self.prefix = prefix
        self.data = ''

    def write(self, data):
        self.stream.write(data)
        self.stream.flush()

        self.data += data
        tmp = str(self.data)
        if '\x0a' in tmp or '\x0d' in tmp:
            tmp = tmp.rstrip('\x0a\x0d')
            log.info('%s%s' % (self.prefix, tmp))
            self.data = ''


logging.basicConfig(level=logging.INFO,
                    filename='text.log',
                    filemode='a')

sys.stdout = StreamLogger(sys.stdout, '[stdout] ')

print 'test for stdout'

S
Sébastien Loisel

我已经使用 Jacob Gabrielson 接受的解决方案大约 1 年了,但现在不可避免的事情已经发生,我的一个用户希望在 Windows 上使用它。查看其他提出的答案,我认为这些答案中的大多数都未能捕获衍生进程的输出(如原始海报所示);我认为这样做的唯一方法是执行 os.dup2()。我想我已经知道如何回答原始发帖人的exact 问题,并且无需使用 Unix 专用工具 tee:我现在可以捕获我的 Python 程序的所有输出,包括任何生成的 shell命令。这适用于 Windows、Mac 和 Linux。代码如下:

import os, sys, threading, platform

class StreamCapture:
    def __init__(self,stream,writer,echo=True,monkeypatch=None):
        self.active = True
        self.writer = writer
        self.stream = stream
        self.fd = stream.fileno()
        self.echo = echo
        (r,w) = os.pipe()
        self.pipe_read_fd = r
        self.pipe_write_fd = w
        self.dup_fd = os.dup(self.fd)
        os.dup2(w,self.fd)
        self.monkeypatch = monkeypatch if monkeypatch is not None else platform.system()=='Windows'
        if self.monkeypatch:
            self.oldwrite = stream.write
            stream.write = lambda z: os.write(self.fd,z.encode() if type(z)==str else z)
        t = threading.Thread(target=self.printer)
        self.thread = t
        t.start()
    def printer(self):
        while True:
            data = os.read(self.pipe_read_fd,100000)
            if(len(data)==0):
                self.writer.close()
                os.close(self.dup_fd)
                os.close(self.pipe_read_fd)
                return
            self.writer.write(data)
            if self.echo:
                os.write(self.dup_fd,data)
    def close(self):
        if not self.active:
            return
        self.active = False
        self.stream.flush()
        if self.monkeypatch:
            self.stream.write = self.oldwrite
        os.dup2(self.dup_fd,self.fd)
        os.close(self.pipe_write_fd)
    def __enter__(self):
        return self
    def __exit__(self,a,b,c):
        self.close()

你像这样使用它(注意除了 Jacob Gabrielson 的解决方案之外的其他解决方案无法捕获的困难案例):

print("This does not get saved to the log file")
with StreamCapture(sys.stdout,open('logfile.txt','wb')):
        os.write(sys.stdout.fileno(),b"Hello, captured world!\n")
        os.system('echo Hello from the shell')     # Hard case
        print("More capturing")
print("This also does not get saved to the log file")

这不是一个简短而甜蜜的答案,但我尽量保持简洁,并且尽可能简单。这很复杂,原因如下:

由于我不能使用 tee,我必须以某种方式从我的 Python 进程中执行 tee 的任务。我不清楚是否存在一种可移植的 fork()ing 和与 os.pipe() 通信的方式(这表明在 Windows 中很难与分叉进程共享文件描述符),所以我决定使用线程。在 Windows 中,当 sys.stdout 和 sys.stderr 的底层 fileno() 通过 os.dup2() 通过 os.pipe() 重新路由时,它们真的不受欢迎。 Python 解释器在第一个 print(...) 命令后立即崩溃。仅在 Windows 上,为了解决解释器崩溃,我通过将其设置为一个简单地调用 os.write(...) 的新函数来猴子补丁 sys.stdout.write = ...。默认情况下,我只在检测到 Windows 时执行此操作。因为我做了猴子补丁,我希望这会到达所有对 sys.stdout 的缓存引用。我选择了这种猴子补丁方法,而不是分配一个全新的流,例如 sys.stdout=...,因为我担心旧的 sys.stdout 的副本会保留在解释器的各个部分中,但我猜想是 sys.stdout 的。 stdout.write 不太可能被直接缓存。如果您对处理管道输出的线程进行守护进程,则该线程会在主线程完成后立即被终止,但这并不能保证所有输出都已写入日志文件。实际上有必要不守护这些辅助线程,并让它们在管道关闭时优雅地终止自己。

实际上,我并不完全确定我是否正确地解决了所有极端情况——与微妙的操作系统功能交互的线程代码编写起来很可怕。尽管如此,它到目前为止通过了我的测试。因为它有点毛茸茸,所以我做了一个 PyPI 包:

pip install streamcapture

Github 是 here


我在单元测试中使用它来断言外部非 python 库的输出并且它的工作完美,谢谢。
别客气!
S
Stu Thompson

上面的答案似乎都没有真正回答所提出的问题。我知道这是一个旧线程,但我认为这个问题比每个人都简单得多:

class tee_err(object):

 def __init__(self):
    self.errout = sys.stderr

    sys.stderr = self

    self.log = 'logfile.log'
    log = open(self.log,'w')
    log.close()

 def write(self, line):

    log = open(self.log,'a')
    log.write(line)
    log.close()   

    self.errout.write(line)

现在这将对正常的 sys.stderr 处理程序和您的文件重复所有操作。为 sys.stdout 创建另一个类 tee_out


两年前发布了一个类似的更好答案:stackoverflow.com/a/616686。您的方法非常开销很大:每次调用 tee=tee_err();tee.write('');tee.write('');... 都会为每个 write 打开+关闭一个文件。有关反对这种做法的论据,请参见 stackoverflow.com/q/4867468stackoverflow.com/q/164053
C
Community

根据@user5359531 在@John T 的answer 下的评论中的请求,这是该答案中链接讨论的修订版的引用帖子的副本:

Issue of redirecting the stdout to both file and screen
Gabriel Genellina gagsl-py2 at yahoo.com.ar
Mon May 28 12:45:51 CEST 2007

    Previous message: Issue of redirecting the stdout to both file and screen
    Next message: Formal interfaces with Python
    Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

En Mon, 28 May 2007 06:17:39 -0300, 人言落日是天涯,望极天涯不见家
<kelvin.you at gmail.com> escribió:

> I wanna print the log to both the screen and file, so I simulatered a
> 'tee'
>
> class Tee(file):
>
>     def __init__(self, name, mode):
>         file.__init__(self, name, mode)
>         self.stdout = sys.stdout
>         sys.stdout = self
>
>     def __del__(self):
>         sys.stdout = self.stdout
>         self.close()
>
>     def write(self, data):
>         file.write(self, data)
>         self.stdout.write(data)
>
> Tee('logfile', 'w')
> print >>sys.stdout, 'abcdefg'
>
> I found that it only output to the file, nothing to screen. Why?
> It seems the 'write' function was not called when I *print* something.

You create a Tee instance and it is immediately garbage collected. I'd
restore sys.stdout on Tee.close, not __del__ (you forgot to call the
inherited __del__ method, btw).
Mmm, doesn't work. I think there is an optimization somewhere: if it looks
like a real file object, it uses the original file write method, not yours.
The trick would be to use an object that does NOT inherit from file:

import sys
class TeeNoFile(object):
     def __init__(self, name, mode):
         self.file = open(name, mode)
         self.stdout = sys.stdout
         sys.stdout = self
     def close(self):
         if self.stdout is not None:
             sys.stdout = self.stdout
             self.stdout = None
         if self.file is not None:
             self.file.close()
             self.file = None
     def write(self, data):
         self.file.write(data)
         self.stdout.write(data)
     def flush(self):
         self.file.flush()
         self.stdout.flush()
     def __del__(self):
         self.close()

tee=TeeNoFile('logfile', 'w')
print 'abcdefg'
print 'another line'
tee.close()
print 'screen only'
del tee # should do nothing

--
Gabriel Genellina

c
cognitiaclaeves

我正在编写一个脚本来运行命令行脚本。 (因为在某些情况下,Linux 命令没有可行的替代品——例如 rsync 的情况。)

我真正想要的是在任何可能的情况下都使用默认的 python 日志记录机制,但是当出现意外的错误时仍然捕获任何错误。

这段代码似乎可以解决问题。它可能不是特别优雅或高效(尽管它不使用 string+=string,所以至少它没有那个特定的潜在瓶颈)。我发布它以防它给其他人任何有用的想法。

import logging
import os, sys
import datetime

# Get name of module, use as application name
try:
  ME=os.path.split(__file__)[-1].split('.')[0]
except:
  ME='pyExec_'

LOG_IDENTIFIER="uuu___( o O )___uuu "
LOG_IDR_LENGTH=len(LOG_IDENTIFIER)

class PyExec(object):

  # Use this to capture all possible error / output to log
  class SuperTee(object):
      # Original reference: http://mail.python.org/pipermail/python-list/2007-May/442737.html
      def __init__(self, name, mode):
          self.fl = open(name, mode)
          self.fl.write('\n')
          self.stdout = sys.stdout
          self.stdout.write('\n')
          self.stderr = sys.stderr

          sys.stdout = self
          sys.stderr = self

      def __del__(self):
          self.fl.write('\n')
          self.fl.flush()
          sys.stderr = self.stderr
          sys.stdout = self.stdout
          self.fl.close()

      def write(self, data):
          # If the data to write includes the log identifier prefix, then it is already formatted
          if data[0:LOG_IDR_LENGTH]==LOG_IDENTIFIER:
            self.fl.write("%s\n" % data[LOG_IDR_LENGTH:])
            self.stdout.write(data[LOG_IDR_LENGTH:])

          # Otherwise, we can give it a timestamp
          else:

            timestamp=str(datetime.datetime.now())
            if 'Traceback' == data[0:9]:
              data='%s: %s' % (timestamp, data)
              self.fl.write(data)
            else:
              self.fl.write(data)

            self.stdout.write(data)


  def __init__(self, aName, aCmd, logFileName='', outFileName=''):

    # Using name for 'logger' (context?), which is separate from the module or the function
    baseFormatter=logging.Formatter("%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")
    errorFormatter=logging.Formatter(LOG_IDENTIFIER + "%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")

    if logFileName:
      # open passed filename as append
      fl=logging.FileHandler("%s.log" % aName)
    else:
      # otherwise, use log filename as a one-time use file
      fl=logging.FileHandler("%s.log" % aName, 'w')

    fl.setLevel(logging.DEBUG)
    fl.setFormatter(baseFormatter)

    # This will capture stdout and CRITICAL and beyond errors

    if outFileName:
      teeFile=PyExec.SuperTee("%s_out.log" % aName)
    else:
      teeFile=PyExec.SuperTee("%s_out.log" % aName, 'w')

    fl_out=logging.StreamHandler( teeFile )
    fl_out.setLevel(logging.CRITICAL)
    fl_out.setFormatter(errorFormatter)

    # Set up logging
    self.log=logging.getLogger('pyExec_main')
    log=self.log

    log.addHandler(fl)
    log.addHandler(fl_out)

    print "Test print statement."

    log.setLevel(logging.DEBUG)

    log.info("Starting %s", ME)
    log.critical("Critical.")

    # Caught exception
    try:
      raise Exception('Exception test.')
    except Exception,e:
      log.exception(str(e))

    # Uncaught exception
    a=2/0


PyExec('test_pyExec',None)

显然,如果您不像我那样受制于奇思妙想,请将 LOG_IDENTIFIER 替换为另一个您不希望看到有人写入日志的字符串。


J
Jensen Taylor

如果您希望将所有输出记录到文件并将其输出到文本文件,则可以执行以下操作。这有点hacky,但它有效:

import logging
debug = input("Debug or not")
if debug == "1":
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string):
        old_print(string)
        logging.info(string)
print("OMG it works!")

编辑:请注意,除非您将 sys.stderr 重定向到 sys.stdout,否则这不会记录错误

EDIT2:第二个问题是您必须传递 1 个参数,这与内置函数不同。

EDIT3:查看代码之前将标准输入和标准输出写入控制台并使用标准错误写入文件

import logging, sys
debug = input("Debug or not")
if debug == "1":
    old_input = input
    sys.stderr.write = logging.info
    def input(string=""):
        string_in = old_input(string)
        logging.info("STRING IN " + string_in)
        return string_in
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string="", string2=""):
        old_print(string, string2)
        logging.info(string)
        logging.info(string2)
print("OMG")
b = input()
print(a) ## Deliberate error for testing

E
Eric H.

您也可以根据上面的 shx2's answer 使用 class multifile 添加 stderr

class Log(object):

    def __init__(self, path_log, mode="w", encoding="utf-8"):
        h = open(path_log, mode, encoding=encoding)
        sys.stdout = multifile([ sys.stdout, h ])
        sys.stderr = multifile([ sys.stderr, h ])

    def __enter__(self):
        """ Necessary if called by with (or with... as) """
        return self     # only necessary if "as"

    def __exit__(self, type, value, tb):
        """ Necessary if call by with """
        pass

    def __del__(self):
        if sys is not None:
            # restoring
            sys.stdout = sys.__stdout__
            sys.stderr = sys.__stderr__

log = Log("test.txt")
print("line 1")
print("line 2", file=sys.stderr)
del log
print("line 3 only on screen")

唯一的缺点是在 python 2.x 中引发异常时:消息似乎丢失了。
N
Nearoo

这是一个临时将标准输出复制到文件的上下文管理器。在我看来这是一个改进,因为它重置了 sys.stdout &即使发生异常也会关闭文件,并且语法指示背景中的不可见变化。扩展了 John T 的解决方案。

class DuplicateStdout:
    def __init__(self, path):
        self.stdout = sys.stdout
        self.path = path
        self.f = None
    
    def write(self, s):
        self.stdout.write(s)
        self.f.write(s)

    def __enter__(self):
        self.f = open(self.path, "w")
        sys.stdout = self
    
    def __exit__(self, *args):
        sys.stdout = self.stdout
        self.f.close()

示例用法:

with DuplicateStdout("foo.log"):
    print("Hey") # also in foo.log

print("There") # not in foo.log

u
user

我为 sys.stderr 编写了一个完整的替换,只是复制了将 stderr 重命名为 stdout 的代码,以使其也可用于替换 sys.stdout

为此,我创建了与当前 stderrstdout 相同的对象类型,并将所有方法转发到原始系统 stderrstdout

import os
import sys
import logging

class StdErrReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        https://stackoverflow.com/questions/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        https://stackoverflow.com/questions/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stderr` permanently.
        """
        global _stderr_singleton
        global _stderr_default
        global _stderr_default_class_type

        # On Sublime Text, `sys.__stderr__` is set to None, because they already replaced `sys.stderr`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stderr__:
            sys.__stderr__ = sys.stderr

        try:
            _stderr_default
            _stderr_default_class_type

        except NameError:
            _stderr_default = sys.stderr
            _stderr_default_class_type = type( _stderr_default )

        # Recreate the sys.stderr logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stderr_write = _stderr_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stderr_write
            global _sys_stderr_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stderr_write`
            def _sys_stderr_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    https://stackoverflow.com/questions/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stderr_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stderr_write` function pointer ever
            try:
                _sys_stderr_write

            except NameError:

                def _sys_stderr_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stderr.write` and our custom wrapper around it.
                    """
                    _sys_stderr_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stderr_singleton

        except NameError:

            class StdErrReplamentHidden(_stderr_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    https://stackoverflow.com/questions/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stderr_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stderr_default.__abstractmethods__

                if hasattr( _stderr_default, "__base__" ):
                    __base__ = _stderr_default.__base__

                if hasattr( _stderr_default, "__bases__" ):
                    __bases__ = _stderr_default.__bases__

                if hasattr( _stderr_default, "__basicsize__" ):
                    __basicsize__ = _stderr_default.__basicsize__

                if hasattr( _stderr_default, "__call__" ):
                    __call__ = _stderr_default.__call__

                if hasattr( _stderr_default, "__class__" ):
                    __class__ = _stderr_default.__class__

                if hasattr( _stderr_default, "__delattr__" ):
                    __delattr__ = _stderr_default.__delattr__

                if hasattr( _stderr_default, "__dict__" ):
                    __dict__ = _stderr_default.__dict__

                if hasattr( _stderr_default, "__dictoffset__" ):
                    __dictoffset__ = _stderr_default.__dictoffset__

                if hasattr( _stderr_default, "__dir__" ):
                    __dir__ = _stderr_default.__dir__

                if hasattr( _stderr_default, "__doc__" ):
                    __doc__ = _stderr_default.__doc__

                if hasattr( _stderr_default, "__eq__" ):
                    __eq__ = _stderr_default.__eq__

                if hasattr( _stderr_default, "__flags__" ):
                    __flags__ = _stderr_default.__flags__

                if hasattr( _stderr_default, "__format__" ):
                    __format__ = _stderr_default.__format__

                if hasattr( _stderr_default, "__ge__" ):
                    __ge__ = _stderr_default.__ge__

                if hasattr( _stderr_default, "__getattribute__" ):
                    __getattribute__ = _stderr_default.__getattribute__

                if hasattr( _stderr_default, "__gt__" ):
                    __gt__ = _stderr_default.__gt__

                if hasattr( _stderr_default, "__hash__" ):
                    __hash__ = _stderr_default.__hash__

                if hasattr( _stderr_default, "__init__" ):
                    __init__ = _stderr_default.__init__

                if hasattr( _stderr_default, "__init_subclass__" ):
                    __init_subclass__ = _stderr_default.__init_subclass__

                if hasattr( _stderr_default, "__instancecheck__" ):
                    __instancecheck__ = _stderr_default.__instancecheck__

                if hasattr( _stderr_default, "__itemsize__" ):
                    __itemsize__ = _stderr_default.__itemsize__

                if hasattr( _stderr_default, "__le__" ):
                    __le__ = _stderr_default.__le__

                if hasattr( _stderr_default, "__lt__" ):
                    __lt__ = _stderr_default.__lt__

                if hasattr( _stderr_default, "__module__" ):
                    __module__ = _stderr_default.__module__

                if hasattr( _stderr_default, "__mro__" ):
                    __mro__ = _stderr_default.__mro__

                if hasattr( _stderr_default, "__name__" ):
                    __name__ = _stderr_default.__name__

                if hasattr( _stderr_default, "__ne__" ):
                    __ne__ = _stderr_default.__ne__

                if hasattr( _stderr_default, "__new__" ):
                    __new__ = _stderr_default.__new__

                if hasattr( _stderr_default, "__prepare__" ):
                    __prepare__ = _stderr_default.__prepare__

                if hasattr( _stderr_default, "__qualname__" ):
                    __qualname__ = _stderr_default.__qualname__

                if hasattr( _stderr_default, "__reduce__" ):
                    __reduce__ = _stderr_default.__reduce__

                if hasattr( _stderr_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stderr_default.__reduce_ex__

                if hasattr( _stderr_default, "__repr__" ):
                    __repr__ = _stderr_default.__repr__

                if hasattr( _stderr_default, "__setattr__" ):
                    __setattr__ = _stderr_default.__setattr__

                if hasattr( _stderr_default, "__sizeof__" ):
                    __sizeof__ = _stderr_default.__sizeof__

                if hasattr( _stderr_default, "__str__" ):
                    __str__ = _stderr_default.__str__

                if hasattr( _stderr_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stderr_default.__subclasscheck__

                if hasattr( _stderr_default, "__subclasses__" ):
                    __subclasses__ = _stderr_default.__subclasses__

                if hasattr( _stderr_default, "__subclasshook__" ):
                    __subclasshook__ = _stderr_default.__subclasshook__

                if hasattr( _stderr_default, "__text_signature__" ):
                    __text_signature__ = _stderr_default.__text_signature__

                if hasattr( _stderr_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stderr_default.__weakrefoffset__

                if hasattr( _stderr_default, "mro" ):
                    mro = _stderr_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stderr_default )` constructor, so we can 
                        instantiate any kind of `sys.stderr` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stderr_default, attribute ):

                            base_class_attribute = super( _stderr_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stderr_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stderr.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stderr_write

                    try:
                        return _stderr_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stderr_default_class_type, _stderr_default ).__getattribute__( item )

            _stderr_singleton = StdErrReplamentHidden()
            sys.stderr = _stderr_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stderr` writer from `sys.stderr` and allow the next call to `lock()` create
            a new writer for the stderr.
        """

        if cls.is_active:
            global _sys_stderr_write_hidden

            cls.is_active = False
            _sys_stderr_write_hidden = _stderr_default.write



class StdOutReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        https://stackoverflow.com/questions/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        https://stackoverflow.com/questions/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stdout` permanently.
        """
        global _stdout_singleton
        global _stdout_default
        global _stdout_default_class_type

        # On Sublime Text, `sys.__stdout__` is set to None, because they already replaced `sys.stdout`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stdout__:
            sys.__stdout__ = sys.stdout

        try:
            _stdout_default
            _stdout_default_class_type

        except NameError:
            _stdout_default = sys.stdout
            _stdout_default_class_type = type( _stdout_default )

        # Recreate the sys.stdout logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stdout_write = _stdout_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stdout_write
            global _sys_stdout_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stdout_write`
            def _sys_stdout_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    https://stackoverflow.com/questions/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stdout_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stdout_write` function pointer ever
            try:
                _sys_stdout_write

            except NameError:

                def _sys_stdout_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stdout.write` and our custom wrapper around it.
                    """
                    _sys_stdout_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stdout_singleton

        except NameError:

            class StdOutReplamentHidden(_stdout_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    https://stackoverflow.com/questions/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stdout_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stdout_default.__abstractmethods__

                if hasattr( _stdout_default, "__base__" ):
                    __base__ = _stdout_default.__base__

                if hasattr( _stdout_default, "__bases__" ):
                    __bases__ = _stdout_default.__bases__

                if hasattr( _stdout_default, "__basicsize__" ):
                    __basicsize__ = _stdout_default.__basicsize__

                if hasattr( _stdout_default, "__call__" ):
                    __call__ = _stdout_default.__call__

                if hasattr( _stdout_default, "__class__" ):
                    __class__ = _stdout_default.__class__

                if hasattr( _stdout_default, "__delattr__" ):
                    __delattr__ = _stdout_default.__delattr__

                if hasattr( _stdout_default, "__dict__" ):
                    __dict__ = _stdout_default.__dict__

                if hasattr( _stdout_default, "__dictoffset__" ):
                    __dictoffset__ = _stdout_default.__dictoffset__

                if hasattr( _stdout_default, "__dir__" ):
                    __dir__ = _stdout_default.__dir__

                if hasattr( _stdout_default, "__doc__" ):
                    __doc__ = _stdout_default.__doc__

                if hasattr( _stdout_default, "__eq__" ):
                    __eq__ = _stdout_default.__eq__

                if hasattr( _stdout_default, "__flags__" ):
                    __flags__ = _stdout_default.__flags__

                if hasattr( _stdout_default, "__format__" ):
                    __format__ = _stdout_default.__format__

                if hasattr( _stdout_default, "__ge__" ):
                    __ge__ = _stdout_default.__ge__

                if hasattr( _stdout_default, "__getattribute__" ):
                    __getattribute__ = _stdout_default.__getattribute__

                if hasattr( _stdout_default, "__gt__" ):
                    __gt__ = _stdout_default.__gt__

                if hasattr( _stdout_default, "__hash__" ):
                    __hash__ = _stdout_default.__hash__

                if hasattr( _stdout_default, "__init__" ):
                    __init__ = _stdout_default.__init__

                if hasattr( _stdout_default, "__init_subclass__" ):
                    __init_subclass__ = _stdout_default.__init_subclass__

                if hasattr( _stdout_default, "__instancecheck__" ):
                    __instancecheck__ = _stdout_default.__instancecheck__

                if hasattr( _stdout_default, "__itemsize__" ):
                    __itemsize__ = _stdout_default.__itemsize__

                if hasattr( _stdout_default, "__le__" ):
                    __le__ = _stdout_default.__le__

                if hasattr( _stdout_default, "__lt__" ):
                    __lt__ = _stdout_default.__lt__

                if hasattr( _stdout_default, "__module__" ):
                    __module__ = _stdout_default.__module__

                if hasattr( _stdout_default, "__mro__" ):
                    __mro__ = _stdout_default.__mro__

                if hasattr( _stdout_default, "__name__" ):
                    __name__ = _stdout_default.__name__

                if hasattr( _stdout_default, "__ne__" ):
                    __ne__ = _stdout_default.__ne__

                if hasattr( _stdout_default, "__new__" ):
                    __new__ = _stdout_default.__new__

                if hasattr( _stdout_default, "__prepare__" ):
                    __prepare__ = _stdout_default.__prepare__

                if hasattr( _stdout_default, "__qualname__" ):
                    __qualname__ = _stdout_default.__qualname__

                if hasattr( _stdout_default, "__reduce__" ):
                    __reduce__ = _stdout_default.__reduce__

                if hasattr( _stdout_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stdout_default.__reduce_ex__

                if hasattr( _stdout_default, "__repr__" ):
                    __repr__ = _stdout_default.__repr__

                if hasattr( _stdout_default, "__setattr__" ):
                    __setattr__ = _stdout_default.__setattr__

                if hasattr( _stdout_default, "__sizeof__" ):
                    __sizeof__ = _stdout_default.__sizeof__

                if hasattr( _stdout_default, "__str__" ):
                    __str__ = _stdout_default.__str__

                if hasattr( _stdout_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stdout_default.__subclasscheck__

                if hasattr( _stdout_default, "__subclasses__" ):
                    __subclasses__ = _stdout_default.__subclasses__

                if hasattr( _stdout_default, "__subclasshook__" ):
                    __subclasshook__ = _stdout_default.__subclasshook__

                if hasattr( _stdout_default, "__text_signature__" ):
                    __text_signature__ = _stdout_default.__text_signature__

                if hasattr( _stdout_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stdout_default.__weakrefoffset__

                if hasattr( _stdout_default, "mro" ):
                    mro = _stdout_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stdout_default )` constructor, so we can 
                        instantiate any kind of `sys.stdout` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stdout_default, attribute ):

                            base_class_attribute = super( _stdout_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stdout_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stdout.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stdout_write

                    try:
                        return _stdout_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stdout_default_class_type, _stdout_default ).__getattribute__( item )

            _stdout_singleton = StdOutReplamentHidden()
            sys.stdout = _stdout_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stdout` writer from `sys.stdout` and allow the next call to `lock()` create
            a new writer for the stdout.
        """

        if cls.is_active:
            global _sys_stdout_write_hidden

            cls.is_active = False
            _sys_stdout_write_hidden = _stdout_default.write

要使用它,您只需调用 StdErrReplament::lock(logger)StdOutReplament::lock(logger) 并传递您要用于发送输出文本的记录器。例如:

import os
import sys
import logging

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

file_handler = logging.FileHandler( log_file_path, 'a' )
file_handler.formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )

log.file_handler = file_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

运行此代码,您将在屏幕上看到:

https://i.stack.imgur.com/7jPuY.png

并在文件内容上:

https://i.stack.imgur.com/xgFZS.png

如果您还想在屏幕上查看 log.debug 调用的内容,则需要将流处理程序添加到您的记录器。在这种情况下,它将是这样的:

import os
import sys
import logging

class ContextFilter(logging.Filter):
    """ This filter avoids duplicated information to be displayed to the StreamHandler log. """
    def filter(self, record):
        return not "_duplicated_from_file" in record.__dict__

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

stream_handler = logging.StreamHandler()
file_handler = logging.FileHandler( log_file_path, 'a' )

formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )
file_handler.formatter = formatter
stream_handler.formatter = formatter
stream_handler.addFilter( ContextFilter() )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )
log.addHandler( stream_handler )

log.file_handler = file_handler
log.stream_handler = stream_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

运行时会这样输出:

https://i.stack.imgur.com/RYHqg.png

虽然它仍会将其保存到文件 my_log_file.txt

https://i.stack.imgur.com/ZAcoY.png

当用 StdErrReplament:unlock() 禁用它时,它只会恢复 stderr 流的标准行为,因为附加的记录器不能永远分离,因为其他人可以引用它的旧版本。这就是为什么它是一个永远不会死的全局单例。因此,如果使用 imp 或其他内容重新加载此模块,它将永远不会重新捕获当前的 sys.stderr,因为它已经被注入并在内部保存。


复制流的意外复杂性达到了惊人的水平。