KDecker
KDecker

Reputation: 7148

Redirect stdout to log file while still printing to stdout?

I would like to capture the console output at the end of a Python script. That is, I want to both print to console as normal and, at the end of execution, save the console output to a file.

I have seen various related SO questions 1, 2, 3 though they either simply redirect the output and not display it or use logging. From what I can tell from reading the logging doc you can only log output from the code you've written.

The issue with all the links above is console output from code you have not written that also prints to console. I want the entire console output of the program at the end of execution.

My first instinct was something along the lines of

logFile = open('LogFile.txt', 'w')
def print_log(msg):
    print(msg)
    logFile.write(msg)

print_log('Hello World!')

logFile.close()

But this would still fail to capture console output from other code and libraries in use. Is there a way to save a python script's console output at the end of execution? I feel like there should be a simple way to do this but all my research has led to no appropriate solution.

Upvotes: 3

Views: 996

Answers (1)

Qeek
Qeek

Reputation: 1970

I've used this one in one of my projects:

import io
import sys
from enum import Enum


class Tee(io.StringIO):
    class Source(Enum):
        STDOUT = 1
        STDERR = 2

    def __init__(self, clone=Source.STDOUT, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._clone = clone

        if clone == Tee.Source.STDOUT:
            self._out = sys.stdout
        elif clone == Tee.Source.STDERR:
            self._out = sys.stderr
        else:
            raise ValueError("Clone has to be STDOUT or STDERR.")

    def write(self, *args, **kwargs):
        self._out.write(*args, **kwargs)
        return super().write(*args, **kwargs)

    def __enter__(self):
        if self._clone == Tee.Source.STDOUT:
            sys.stdout = self
        else:
            sys.stderr = self
        self.seek(io.SEEK_END)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._clone == Tee.Source.STDOUT:
            sys.stdout = self._out
        else:
            sys.stderr = self._out
        self.seek(0)
        return False

Basically it does exactly what Maksym Markov said in the comment with one difference. I usually don't wanna stall any outputs so I've written this Tee which capture all text going on stdout (or stderr) immediately prints it and save into the buffer for later usage. It also take care about "fixing" the sys.stdout when the code exits the with block.

The example of usage:

if __name__ == "__main__":
    with Tee() as tee:
        print("Hello World!")

    print(tee.read())

There are some drawbacks like without additional code you can't use tee.read() inside the with block. But in my case I always need process the output of the whole block.

Upvotes: 1

Related Questions