How to duplicate sys.stdout to a log file?

前端 未结 17 895
醉酒成梦
醉酒成梦 2020-11-22 06:54

Edit: Since it appears that there\'s either no solution, or I\'m doing something so non-standard that nobody knows - I\'ll revise my question to also ask: What is the best w

相关标签:
17条回答
  • 2020-11-22 07:37

    Here is another solution, which is more general than the others -- it supports splitting output (written to sys.stdout) to any number of file-like objects. There's no requirement that __stdout__ itself is included.

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

    NOTE: This is a proof-of-concept. The implementation here is not complete, as it only wraps methods of the file-like objects (e.g. write), leaving out members/properties/setattr, etc. However, it is probably good enough for most people as it currently stands.

    What I like about it, other than its generality, is that it is clean in the sense it doesn't make any direct calls to write, flush, os.dup2, etc.

    0 讨论(0)
  • 2020-11-22 07:38

    I wrote a tee() implementation in Python that should work for most cases, and it works on Windows also.

    https://github.com/pycontribs/tendo

    Also, you can use it in combination with logging module from Python if you want.

    0 讨论(0)
  • 2020-11-22 07:40

    What you really want is logging module from standard library. Create a logger and attach two handlers, one would be writing to a file and the other to stdout or stderr.

    See Logging to multiple destinations for details

    0 讨论(0)
  • 2020-11-22 07:40

    You can also add stderr as well, based on shx2's answer above using 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")
    
    0 讨论(0)
  • 2020-11-22 07:43

    Since you're comfortable spawning external processes from your code, you could use tee itself. I don't know of any Unix system calls that do exactly what tee does.

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

    You could also emulate tee using the multiprocessing package (or use processing if you're using Python 2.5 or earlier).

    Update

    Here is a Python 3.3+-compatible version:

    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)
    
    0 讨论(0)
  • 2020-11-22 07:46

    If you wish to log all output to a file AND output it to a text file then you can do the following. It's a bit hacky but it works:

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

    EDIT: Note that this does not log errors unless you redirect sys.stderr to sys.stdout

    EDIT2: A second issue is that you have to pass 1 argument unlike with the builtin function.

    EDIT3: See the code before to write stdin and stdout to console and file with stderr only going to file

    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 讨论(0)
提交回复
热议问题