web-dev-qa-db-ja.com

sys.stdoutをログファイルに複製する方法は?

編集:解決策がないか、誰も知らないほど非標準的なことをしているように見えるので、質問を修正して質問します:pythonアプリは多くのシステムコールを行っていますか?

私のアプリには2つのモードがあります。対話モードでは、すべての出力を画面とログファイル(システムコールからの出力を含む)に出力します。デーモンモードでは、すべての出力がログに記録されます。デーモンモードは、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())

これの良いところは、残りのコードからの特別な印刷呼び出しを必要としないことです。このコードはいくつかのシェルコマンドも実行するため、各出力を個別に処理する必要はありません。

単純に、リダイレクトの代わりにduplicatingを除いて同じことを行いたいと思います。

最初は、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」は、画面に表示されたものと同じでなければなりません。

138
drue

コードから外部プロセスを生成するのが快適なので、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)
50

以前にも同じ問題がありましたが、このスニペットは非常に便利です。

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

from: http://mail.python.org/pipermail/python-list/2007-May/438106.html

131
John T

printステートメントは、sys.stdoutに割り当てるオブジェクトのwrite()メソッドを呼び出します。

一度に2つの場所に書き込むために小さなクラスをスピンアップします...

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>に戻す必要があります。
  • 一度に複数のログファイルに書き込む機能、または異なるログレベルなどを処理する機能が必要になる場合があります。

これらはすべて単純明快なので、読者の練習問題として残してもかまいません。ここでの重要な洞察は、printsys.stdoutに割り当てられた「ファイルのようなオブジェクト」を呼び出すだけです。

71
Triptych

本当に必要なのは、標準ライブラリのloggingモジュールです。ロガーを作成し、2つのハンドラーを接続します。1つはファイルに書き込み、もう1つはstdoutまたはstderrに書き込みます。

複数の宛先へのロギング を参照してください

64

他のソリューションよりも一般的な別のソリューションを次に示します。これは、(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)のmethodsのみをラップし、members/properties/setattrなどを除外するため、完全ではありません。現在のところ、ほとんどの人にとって十分です。

私がそれについて気に入っているのは、その一般性以外に、writeflushos.dup2などを直接呼び出していないという意味でクリーンであるということです。

17
shx2

他の場所で説明したように、おそらく最善の解決策は、ロギングモジュールを直接使用することです。

import logging

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

ただし、stdoutをリダイレクトするためにreally wantを行う(まれな)場合があります。 printを使用するDjangoのrunserverコマンドを拡張しているとき、この状況がありました。Djangoソースをハックしたくありませんでしたが、ファイルに移動するにはprintステートメントが必要でした。

これは、ロギングモジュールを使用してstdoutおよびstderrをシェルからリダイレクトする方法です。

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実装を使用してください。

12
blokeley

ほとんどの場合に機能するPythonでtee()実装を作成しましたが、Windowsでも機能します。

https://github.com/pycontribs/tendo

また、必要に応じて、Pythonのloggingモジュールと組み合わせて使用​​できます。

11
sorin

(ああ、質問を読み直して、これがまったく当てはまらないことを確認してください。)

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 __== "__main__":
    sys.exit(main())
8
Atlas1j

ジョンTの回答を完了するには: https://stackoverflow.com/a/616686/395687

withキーワードを使用してコンテキストマネージャーとして使用する__enter__および__exit__メソッドを追加し、このコードを提供します

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')
8
cladmi

私はこの質問に繰り返し答えられていることを知っていますが、このために John T's answerから主な答えを取り、提案されたフラッシュを含むように修正し、リンクされた改訂版に従いました。また、 cladmi's withステートメントで使用するための回答に記載されているように、enterとexitも追加しました。さらに、 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()
5
Status

ロギングモジュールを使用する別のソリューション:

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'
4
Denis Barmenkov

上記の答えはどれも、提起された問題に本当に答えているようには見えません。私はこれが古いスレッドであることを知っていますが、この問題は誰もが作っているよりもずっと簡単だと思います:

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を作成します。

3
josianator

@John Tの answer の下にあるコメントの@ user5359531によるリクエストに応じて、その回答のリンクされたディスカッションの改訂版への参照された投稿のコピーを以下に示します。

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
2
martineau

Cmd-lineスクリプトを実行するスクリプトを書いています。 (場合によっては、rsyncの場合のように、Linuxコマンドの代わりに実行可能なものがないだけです。)

私が本当に望んでいたのは、可能な限りすべての場合にデフォルトのpythonロギングメカニズムを使用することでした。

このコードはトリックを行うようです。特にエレガントでも効率的でもないかもしれません(string + = stringを使用していませんが、少なくとも特定の潜在的なボトルネックはありません)。他の誰かに有益なアイデアを提供する場合に備えて投稿しています。

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を、誰かがログに書き込むのを見たくない別の文字列に置き換えてください。

1
cognitiaclaeves

すべての出力をファイルに記録し、テキストファイルに出力する場合は、次の操作を実行できます。それは少しハックですが、動作します:

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!")

編集:sys.stderrをsys.stdoutにリダイレクトしない限り、これはエラーをログに記録しないことに注意してください

EDIT2:2番目の問題は、組み込み関数とは異なり、1つの引数を渡す必要があることです。

EDIT3:前のコードを参照してstdinとstdoutをコンソールに書き込み、stderrのみがファイルに行く

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
0
Jensen Taylor