编辑:因为似乎没有解决方案,或者我正在做一些没人知道的非标准的东西 - 我会修改我的问题也要问:当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"应与屏幕上显示的内容相同.
我以前遇到过同样的问题,发现这个片段非常有用:
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
该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()
现在print
语句将回显到屏幕并附加到您的日志文件:
# prints "1 2" toAND log.dat print "%d %d" % (1,2)
这显然是快速而肮脏的.一些说明:
您可能应该对日志文件名进行参数化.
如果您不在程序的持续时间内进行日志记录,则应该将sys.stdout还原为.
您可能希望能够一次写入多个日志文件,或处理不同的日志级别等.
这些都很简单,我很乐意将它们作为练习者留给读者.这里的关键见解是print
只调用分配给的"类文件对象" sys.stdout
.
你真正想要的是logging
来自标准库的模块.创建一个记录器并附加两个处理程序,一个将写入文件,另一个将写入stdout或stderr.
有关详细信息,请参阅 记录到多个目标
由于您可以轻松地从代码中生成外部流程,因此您可以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)
您也可以tee
使用多处理包进行模拟(或者如果您使用的是Python 2.5或更早版本,则使用处理).
这是另一种解决方案,它比其他解决方案更通用 - 它支持将输出(写入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等.但是,它可能是对大多数人来说足够好,因为它目前为.
我喜欢它,比其共性外,就是它是干净的在这个意义上它没有任何直接调用write
,flush
,os.dup2
,等.
如其他地方所述,也许最好的解决方案是直接使用日志记录模块:
import logging logging.basicConfig(level=logging.DEBUG, filename='mylog.log') logging.info('this should to write to the log file')
但是,有一些(罕见的)你真的想要重定向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'
如果您真的无法直接使用日志记录模块,则应该只使用此LogFile实现.
我tee()
在Python中编写了一个适用于大多数情况的实现,它也适用于Windows.
https://github.com/pycontribs/tendo
此外,如果需要,您可以将它与logging
Python中的模块结合使用.
(啊,只是重新阅读你的问题,看看这不太适用.)
这是一个使用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())
要完成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')
我知道这个问题已经反复回答,但为此我从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: /sf/ask/17360801/ 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()