はAdam Rosenfield's codeに基づいて、あなたは
- 使用
select.select
は
- 繰り返した後、その出力を
proc.stdout
かproc.stderr
から読み取るべき出力があるまで、
- 読み込みをブロックし、ログに記録することができプロセスが完了するまで
以下は、/tmp/test.log
に書き込んで、ls -laR /tmp
コマンドを実行することに注意してください。ニーズに合わせて変更してください。
(PS:通常/ tmpがそうls -laR /tmp
がstdoutとstderrの両方に出力を生成し実行する、通常のユーザが読み取ることができないディレクトリが含まれ、それらが生成されるように以下のコードが正しく、これら2つのストリームをインターリーブする。)
import logging
import subprocess
import shlex
import select
import fcntl
import os
import errno
import contextlib
logger = logging.getLogger(__name__)
def make_async(fd):
'''add the O_NONBLOCK flag to a file descriptor'''
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
def read_async(fd):
'''read some data from a file descriptor, ignoring EAGAIN errors'''
try:
return fd.read()
except IOError, e:
if e.errno != errno.EAGAIN:
raise e
else:
return ''
def log_fds(fds):
for fd in fds:
out = read_async(fd)
if out:
logger.info(out)
@contextlib.contextmanager
def plain_logger():
root = logging.getLogger()
hdlr = root.handlers[0]
formatter_orig = hdlr.formatter
hdlr.setFormatter(logging.Formatter('%(message)s'))
yield
hdlr.setFormatter(formatter_orig)
def main():
# fmt = '%(name)-12s: %(levelname)-8s %(message)s'
logging.basicConfig(filename = '/tmp/test.log', mode = 'w',
level = logging.DEBUG)
logger.info("Started")
cmdStr = 'ls -laR /tmp'
with plain_logger():
proc = subprocess.Popen(shlex.split(cmdStr),
stdout = subprocess.PIPE, stderr = subprocess.PIPE)
# without `make_async`, `fd.read` in `read_async` blocks.
make_async(proc.stdout)
make_async(proc.stderr)
while True:
# Wait for data to become available
rlist, wlist, xlist = select.select([proc.stdout, proc.stderr], [], [])
log_fds(rlist)
if proc.poll() is not None:
# Corner case: check if more output was created
# between the last call to read_async and now
log_fds([proc.stdout, proc.stderr])
break
logger.info("Done")
if __name__ == '__main__':
main()
を
編集:
あなたはlogfile = open('/tmp/test.log', 'a')
からstdout
とstderr
をリダイレクトすることができます。 しかし、これを実行するのが少し難しいのは、/tmp/test.log
にも書き込んでいるすべてのロガーハンドラは、サブプロセスが何を書き込んでいるのか分からないため、ログファイルが文字化けする可能性があります。
サブプロセスが業務を行っているときにロギングコールを行わない場合、唯一の問題は、サブプロセスの終了後にロガーハンドラのファイル内の位置が間違っていることです。これは、
handler.stream.seek(0, 2)
を呼び出すことで修正できるため、ハンドラはファイルの最後に書き出しを再開します。
import logging
import subprocess
import contextlib
import shlex
logger = logging.getLogger(__name__)
@contextlib.contextmanager
def suspended_logger():
root = logging.getLogger()
handler = root.handlers[0]
yield
handler.stream.seek(0, 2)
def main():
logging.basicConfig(filename = '/tmp/test.log', filemode = 'w',
level = logging.DEBUG)
logger.info("Started")
with suspended_logger():
cmdStr = 'test2.py 1>>/tmp/test.log 2>&1'
logfile = open('/tmp/test.log', 'a')
proc = subprocess.Popen(shlex.split(cmdStr),
stdout = logfile,
stderr = logfile)
proc.communicate()
logger.info("Done")
if __name__ == '__main__':
main()
これは動作しますが、私は、ログファイルに追加しようとしている出力は、別のプログラムからロガーストリームです。だから、もし私がこれをしたら、私はラインごとに2つのタイムスタンプを得るでしょう(これがはっきりしないかどうか私に教えてください)。では、ロガープレフィックスをバイパスして、不必要なロギングファイル記述子に直接書き込む方法はありますか? – fodon
一時的にロギング形式を '%(message)s 'に変更することができます。上記のようにいくつかのコードを追加しました。 – unutbu
うん、はい、それはうまくいきますが、プロセスにディスクリプタを渡してファイルできればそれほどエレガントではありませんか?本当にそれはできませんか? – fodon