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

编辑:因为它似乎没有解决方案,或者我正在做一些非标准的,没有人知道的东西 – 我会修改我的问题,也问:当一个python应用程序正在做一个完成日志记录的最好方法是什么很多系统调用?

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


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

澄清:

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

# 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 "kljhf sdf" os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {}) os.execve("/bin/ls", ["/bin/ls"], os.environ) 

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

既然你很容易从代码中产生外部进程,你可以使用tee本身。 我不知道有任何的Unix系统调用是做什么的。

 import subprocess, os, sys # Unbuffer output 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或更早的版本,则使用处理 )。

我之前也有这个相同的问题,发现这个片段非常有用:

 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语句将调用您分配给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还原为<stdout>
  • 您可能希望能够一次写入多个日志文件或处理不同的日志级别等。

这些都足够简单,我很乐意让他们作为读者的练习。 这里的关键洞察是print只是调用一个分配给sys.stdout的“类文件对象”。

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

有关详细信息,请参阅记录到多个目标

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

https://github.com/pycontribs/tendo

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

(啊,只是重新读你的问题,看看这不太适用。)

这是一个使用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()) 

这是另一种解决方案,比其他解决方案更通用 – 它支持将输出(写入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等。

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

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

但是,有一些(罕见)的情况下,你真的想重定向标准输出。 当我扩展使用print的django的runserver命令时,我遇到了这种情况:我不想破解django源代码,但需要打印语句去一个文件。

这是一种使用日志记录模块将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实现。

要完成John T答案: https : //stackoverflow.com/a/616686/395687

我添加了一个输入退出方法,使用它作为一个上下文管理器与'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' 

另一个使用日志模块的解决

 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' 

我知道这个问题已经得到了一再的回答,但是我已经从John T的回答中得到了主要答案,并且修改了它,所以它包含了建议的flush,并且遵循了它的链接修订版本。 我还添加了进入和退出,在cladmi的答案中提到与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 

你可以使用它

 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() 

上面的答案都没有真正回答所提出的问题。 我知道这是一个古老的线索,但我认为这个问题比每个人都做得更简单:

 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

根据@ user5359531在@John T的回答下的评论请求,下面是引用帖子的一个副本,链接到该链接讨论的修订版:

 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 

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

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

这段代码似乎有诀窍。 它可能不是特别优雅或高效(虽然它不使用字符串+ =字符串,所以至少它没有这个特定的潜在瓶颈)。 我发布它,以防别人给出任何有用的想法。

 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) 

很显然,如果你不像我那样wh </s>不乐,用另外一个字符串替换LOG_IDENTIFIER,而这个字符串你不会看到有人写入日志。