如何将 sys.stdout 复制到日志文件中?
如何将 sys.stdout 复制到日志文件中?
编辑:由于似乎没有解决方案,或者我正在做一些非常非标准的事情,没有人知道 - 我将修改我的问题,同时提问:当一个Python应用程序进行大量系统调用时,最佳的记录方式是什么?
我的应用程序有两种模式。在交互模式下,我希望所有输出都显示在屏幕上,并记录到日志文件中,包括来自任何系统调用的输出。在守护模式下,所有输出都记录到日志中。使用os.dup2()
,守护模式运行得很好。但是我找不到在交互模式下将所有输出复制到日志中的方法,而不需要修改每个系统调用。
换句话说,我希望Python应用程序生成的任何输出,包括系统调用的输出,都具有类似于命令行'tee'的功能。
为了澄清:
要重定向所有输出,我做了类似这样的操作,效果很好:
# 打开日志文件 so = se = open("%s.log" % self.name, 'w', 0) # 重新打开无缓冲的stdout sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # 将stdout和stderr重定向到上述打开的日志文件 os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno())
这样做的好处是不需要从代码的其他部分进行特殊的打印调用。代码还运行了一些shell命令,因此不必单独处理每个命令的输出也很好。
简单来说,我想做同样的事情,只是复制而不是重定向。
一开始我认为只要简单地颠倒dup2
的顺序就可以。为什么不行呢?以下是我的测试:
import os, sys ### 我的错误解决方案: 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"应该与屏幕上显示的内容相同。
如何将sys.stdout复制到日志文件中?
问题的出现原因:print语句将调用分配给sys.stdout的任何对象的write()方法。
解决方法:
首先,可以创建一个Logger类来同时写入两个位置:
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语句将同时回显到屏幕和追加到日志文件中:
print "%d %d" % (1,2)
这只是一种简单粗暴的方法。还有一些需要注意的事项:
- 可能需要将日志文件名作为参数化。
- 如果在程序运行期间不会记录日志,可能需要将sys.stdout恢复为
- 可能需要能够同时写入多个日志文件,或处理不同的日志级别等。
这些都是非常直接的,我相信读者可以自行练习。关键的见解在于print语句只是调用分配给sys.stdout的“类似文件”的对象。
另外,当Logger被删除时,应该将stdout设置回默认值。
此外,Logger类还应该定义一个flush()方法,例如"def flush(): self.terminal.flush(); self.log.flush()"。
还有一个问题是,如果使用subprocess.call创建一个进程,其输出将会发送到控制台而不是log.dat文件,是否有办法解决这个问题?
对于这个问题,可以将stdout作为参数传入Logger类进行改进。
此外,还有一个问题是错误信息不会被写入日志文件,例如traceback.print_exc()。
这些问题都可以作为改进的方向。
如何将sys.stdout复制到日志文件中?
问题的出现原因:
- 需要将sys.stdout中的输出内容复制到一个日志文件中
解决方法:
- 创建一个名为Tee的类
- 在类的初始化函数中,打开一个文件,并将sys.stdout保存到另一个变量中
- 将sys.stdout重新指定为Tee类的实例
- 在类的析构函数中,将sys.stdout恢复到之前保存的值,并关闭文件
- 实现write函数,将数据写入文件并同时写入到sys.stdout
- 实现flush函数,刷新文件
- 使用Tee类来复制sys.stdout到日志文件中
代码如下:
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()
其他扩展:
- 可以将上述代码实现为contextmanager,以增加灵活性
- 在Python 3中,需要在write方法的末尾调用self.file.flush()来刷新输出
- 注意,该解决方法无法记录由print()和sys.stdout.write()以外的调用生成的输出。对于直接写入stdout fd (1)的C代码或包装在Python中的Fortran代码生成的输出,无法重定向到文件。Gabrielson提供的方法可以解决这个问题
- 注意,IO.write()返回整数,因此为了保持与stdout相同的API,最后一行应该返回self.stdout.write(data),而不是返回None
- 注意,如果没有显式调用,__del__方法将由垃圾回收器触发。对于不熟悉垃圾回收的人来说,使用contextManager可能更安全一些。
如何将sys.stdout复制到一个日志文件中?
问题出现的原因:
在这个问题中,用户希望将sys.stdout的输出内容复制到一个日志文件中。然而,他们并不熟悉如何实现这一点,所以他们寻求解决方案。
解决方法:
在这个问题中,有几种解决方法被提出来。首先,用户可以使用tee自身来完成这个任务。他们可以通过在代码中生成外部进程来调用tee命令,将sys.stdout的输出同时传递给日志文件和屏幕。这个方法使用了subprocess、os和sys等Python库来实现。
此外,用户还可以使用multiprocessing包来模拟tee命令的功能。这个包允许用户在不同的进程中执行任务,并且可以通过将输出重定向到日志文件来复制sys.stdout的内容。
最后,用户还可以在Python 3.3及以上版本中使用os.dup2()函数来实现这个任务。这个函数允许用户将一个文件描述符复制到另一个文件描述符,从而实现对sys.stdout的复制。
通过使用tee命令、multiprocessing包或os.dup2()函数,用户可以将sys.stdout的输出内容复制到一个日志文件中。这些方法提供了不同的实现方式,用户可以根据自己的需求选择最适合的方法。