编辑:既然看起来没有解决方案,或者我做的事情非常标准,没人知道 - 我会修改我的问题也要问:什么是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”应与屏幕上显示的内容相同。
答案 0 :(得分:128)
之前我遇到过同样的问题,发现这个片段非常有用:
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 :(得分:71)
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)
这显然是快速而肮脏的。一些说明:
<stdout>
将不会记录该计划的持续时间。这些都很简单,我很乐意把它们作为读者练习。这里的主要见解是print
只调用分配给sys.stdout
的“类文件对象”。
答案 2 :(得分:64)
您真正想要的是来自标准库的logging
模块。创建一个记录器并附加两个处理程序,一个将写入文件,另一个将写入stdout或stderr。
有关详细信息,请参阅Logging to multiple destinations
答案 3 :(得分:49)
由于您可以轻松地从代码中生成外部流程,因此您可以使用tee
本身。我不知道任何与tee
完全相同的Unix系统调用。
# 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)
答案 4 :(得分:16)
这是另一种解决方案,它比其他解决方案更通用 - 它支持将输出(写入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
),省略了members / properties / setattr等。但是,它可能是好的足以满足大多数人的需求。
我喜欢它,除了它的一般性,它是干净的,因为它不会直接调用write
,flush
,os.dup2
等
答案 5 :(得分:12)
如其他地方所述,也许最好的解决方案是直接使用日志记录模块:
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实现。
答案 6 :(得分:11)
我在Python中编写了一个适用于大多数情况的tee()
实现,它也可以在Windows上运行。
https://github.com/pycontribs/tendo
此外,如果需要,您可以将其与Python中的logging
模块结合使用。
答案 7 :(得分:9)
(啊,只是重新阅读你的问题,看看这不太适用。)
以下是使用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())
答案 8 :(得分:8)
完成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')
答案 9 :(得分:5)
我知道这个问题已经反复回答,但为此我从John T's回答中得到了主答案,并对其进行了修改,使其包含建议的刷新并遵循其链接的修订版本。我还添加了cladmi's回答中提到的enter和exit以与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()
答案 10 :(得分:4)
使用日志记录模块的另一种解决方案:
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'
答案 11 :(得分:3)
上述答案似乎都没有回答所提出的问题。我知道这是一个旧线程,但我认为这个问题比每个人都要简单得多:
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处理程序和您的文件。为tee_out
创建另一个课程sys.stdout
。
答案 12 :(得分:2)
根据@ 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
答案 13 :(得分:1)
我正在编写一个脚本来运行cmd-line脚本。 (因为在某些情况下,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)
显然,如果你不像我那样奇怪,那么将LOG_IDENTIFIER替换为你不喜欢有人写日志的另一个字符串。
答案 14 :(得分:1)
我一直在使用 Jacob Gabrielson 公认的解决方案大约 1 年,但现在不可避免的事情发生了,我的一个用户希望在 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()
你像这样使用它(注意除了 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()
与 os.pipe()
进行通信和通信的可移植方式(this 表示很难与 Windows 中的分叉进程共享文件描述符),因此我决定使用 threading
。
在 Windows 中,sys.stdout
和 sys.stderr
在它们的底层 fileno()
经由 os.pipe()
通过 os.dup2()
重新路由时真的不受欢迎。 Python 解释器在执行第一个 print(...)
命令后立即崩溃。
仅在 Windows 上,为了解决解释器崩溃的问题,我通过将 sys.stdout.write = ...
设置为一个简单地调用 os.write(...)
的新函数来对 sys.stdout
进行猴子补丁。默认情况下,我只在检测到 Windows 时执行此操作。因为我是monkeypatch,我希望这将达到所有缓存的sys.stdout=...
引用。我选择了这种monkeypatching方法而不是分配一个全新的流,例如sys.stdout
,因为我担心旧 sys.stdout.write
的副本会保留在解释器的各个部分中缓存,但我猜测 pip install streamcapture
不太可能被直接缓存。>
如果您守护进程处理管道输出的线程,那么该线程会在主线程完成后立即终止,但这并不能保证所有输出都已写入日志文件。实际上有必要不要守护这些辅助线程,并让它们在管道关闭时优雅地终止自己。
实际上,我并不完全确定我是否正确理解了所有极端情况——与微妙的操作系统功能交互的线程代码编写起来很可怕。尽管如此,到目前为止它通过了我的测试。因为有点毛,我做了个PyPI包:
{{1}}
Github 是 here。
答案 15 :(得分:0)
如果希望将所有输出记录到文件并将其输出到文本文件,则可以执行以下操作。有点黑,但是可以用:
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!")
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
答案 16 :(得分:0)
您还可以根据上述shx2's answer使用stderr
添加class multifile
:
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")
答案 17 :(得分:-1)
我为sys.stderr
写了一个完整的替代品,只是重复了将stderr
重命名为stdout
的代码,使其也可以替换为sys.stdout
。
为此,我创建了与当前stderr
和stdout
相同的对象类型,并将所有方法转发到原始系统stderr
和stdout
:
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" )
运行此代码,您将在屏幕上看到:
关于文件内容:
如果您还希望在屏幕上看到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" )
运行时会输出如下:
虽然它仍会将其保存到文件my_log_file.txt
:
使用StdErrReplament:unlock()
禁用此功能时,它只会恢复stderr
流的标准行为,因为附加的记录器不能永远不会分离,因为其他人可以引用其旧版本。这就是为什么它是一个永远不会死的全球单身人士。因此,如果使用imp
或其他内容重新加载此模块,它将永远不会重新捕获当前sys.stderr
,因为它已经注入并在内部保存。