编辑:因为似乎没有解决方案,或者我正在做一些没人知道的非标准的东西 - 我会修改我的问题也要问:当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())
Run Code Online (Sandbox Code Playgroud)
关于这一点的好处是它不需要来自其余代码的特殊打印调用.该代码还运行一些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)
Run Code Online (Sandbox Code Playgroud)
文件"a.log"应与屏幕上显示的内容相同.
Joh*_*n T 132
我以前遇到过同样的问题,发现这个片段非常有用:
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()
Run Code Online (Sandbox Code Playgroud)
来自:http://mail.python.org/pipermail/python-list/2007-May/438106.html
Tri*_*ych 73
该print语句将调用write()您分配给sys.stdout的任何对象的方法.
我会一个小班,一次写两个地方......
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()
Run Code Online (Sandbox Code Playgroud)
现在print语句将回显到屏幕并附加到您的日志文件:
# prints "1 2" to <stdout> AND log.dat
print "%d %d" % (1,2)
Run Code Online (Sandbox Code Playgroud)
这显然是快速而肮脏的.一些说明:
<stdout>如果您不在程序的持续时间内进行日志记录,则应该将sys.stdout还原为.这些都很简单,我很乐意将它们作为练习者留给读者.这里的关键见解是print只调用分配给的"类文件对象" sys.stdout.
Jac*_*son 50
由于您可以轻松地从代码中生成外部流程,因此您可以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)
Run Code Online (Sandbox Code Playgroud)
您也可以tee使用多处理包进行模拟(或者如果您使用的是Python 2.5或更早版本,则使用处理).
shx*_*hx2 17
这是另一种解决方案,它比其他解决方案更通用 - 它支持将输出(写入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')
Run Code Online (Sandbox Code Playgroud)
注意:这是一个概念验证.这里的实现是不完整的,因为它仅包装方法的类文件对象(例如write),留出成员/属性/ SETATTR等.但是,它可能是对大多数人来说足够好,因为它目前为.
我喜欢它,比其共性外,就是它是干净的在这个意义上它没有任何直接调用write,flush,os.dup2,等.
blo*_*ley 12
如其他地方所述,也许最好的解决方案是直接使用日志记录模块:
import logging
logging.basicConfig(level=logging.DEBUG, filename='mylog.log')
logging.info('this should to write to the log file')
Run Code Online (Sandbox Code Playgroud)
但是,有一些(罕见的)你真的想要重定向stdout.当我扩展使用print的django的runserver命令时,我有这种情况:我不想破解django源但需要print语句转到文件.
这是一种使用日志记录模块将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'
Run Code Online (Sandbox Code Playgroud)
如果您真的无法直接使用日志记录模块,则应该只使用此LogFile实现.
sor*_*rin 11
我tee()在Python中编写了一个适用于大多数情况的实现,它也适用于Windows.
https://github.com/pycontribs/tendo
此外,如果需要,您可以将它与loggingPython中的模块结合使用.
(啊,只是重新阅读你的问题,看看这不太适用.)
这是一个使用python日志记录模块的示例程序.自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())
Run Code Online (Sandbox Code Playgroud)
要完成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
Run Code Online (Sandbox Code Playgroud)
它可以用作
with Tee('outfile.log', 'w'):
print('I am written to both stdout and outfile.log')
Run Code Online (Sandbox Code Playgroud)
我知道这个问题已经反复回答,但为此我从John T的答案中得到了主要答案,并对其进行了修改,使其包含建议的同花顺并遵循其链接的修订版本.我还添加了在cladmi的答案中提到的enter和exit,用于with语句.此外,文档提到使用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
Run Code Online (Sandbox Code Playgroud)
然后你可以使用它
with Logger('My_best_girlie_by_my.side'):
print("we'd sing sing sing")
Run Code Online (Sandbox Code Playgroud)
要么
Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()
Run Code Online (Sandbox Code Playgroud)
我已经使用 Jacob Gabrielson 公认的解决方案大约一年了,但现在不可避免的事情发生了,我的一位用户希望在 Windows 上使用此解决方案。看看其他提出的答案,我认为大多数答案都未能捕获生成进程的输出(如原始海报中粗体所示);我认为做到这一点的唯一方法就是这样做os.dup2()。我想我已经弄清楚如何回答原始发帖人的确切问题,并且无需使用特定于 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()
Run Code Online (Sandbox Code Playgroud)
您可以像这样使用它(请注意除了 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")
Run Code Online (Sandbox Code Playgroud)
这不是一个简短而甜蜜的答案,但我试图保持简洁,并且尽可能简单。由于以下原因,情况很复杂:
由于我无法使用,我必须以某种方式在我的 Python 进程中tee执行 的任务。tee我不清楚是否有一种可移植的方式fork()与 an 进行通信os.pipe()(这表明在 Windows 中很难与分叉进程共享文件描述符),因此我决定使用threading.
在 Windows 中,当它们的底层通过via重新路由时,sys.stdout我sys.stderr真的不喜欢。Python 解释器在执行第一个命令后立即崩溃。fileno()os.pipe()os.dup2()print(...)
仅在 Windows 上,为了解决解释器崩溃问题,我sys.stdout.write = ...通过将其设置为一个仅调用os.write(...). 默认情况下,我仅在检测到 Windows 时执行此操作。因为我使用了monkeypatch,所以我希望这能到达所有缓存的sys.stdout. 我选择了这种猴子补丁方法,而不是分配一个全新的流,例如sys.stdout=...,因为我担心旧流的副本sys.stdout将保留在解释器的各个部分中缓存,但我猜测这sys.stdout.write不太可能被直接缓存。
如果您守护进程处理管道输出的线程,那么主线程完成后该线程就会被终止,但这并不能保证所有输出都已写入日志文件。实际上,有必要不要对这些辅助线程进行守护进程,并让它们在管道关闭时优雅地自行终止。
实际上,我并不完全确定我是否正确理解了所有极端情况——与微妙的操作系统功能交互的线程代码很难编写。尽管如此,到目前为止它还是通过了我的测试。因为有点毛茸茸的,所以我做了一个PyPI包:
pip install streamcapture
Run Code Online (Sandbox Code Playgroud)
Github 在这里。
| 归档时间: |
|
| 查看次数: |
97006 次 |
| 最近记录: |