117

I want to run a python script and capture the output on a text file as well as want to show on console.

I want to specify it as a property of the python script itself. NOT to use the command echo "hello world" | tee test.txt on command prompt every time.

Within script I tried:

sys.stdout = open('log.txt','w')

But this does not show the stdout output on screen.

I have heard about logging module but I could not get luck using that module to do the job.

user
  • 1,220
  • 1
  • 12
  • 31
user2033758
  • 1,848
  • 3
  • 16
  • 16

16 Answers16

190

You can use shell redirection while executing the Python file:

python foo_bar.py > file

This will write all results being printed on stdout from the Python source to file to the logfile.

Or if you want logging from within the script:

import sys

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("logfile.log", "a")
   
    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

    def flush(self):
        # this flush method is needed for python 3 compatibility.
        # this handles the flush command by doing nothing.
        # you might want to specify some extra behavior here.
        pass    

sys.stdout = Logger()

Now you can use:

print "Hello"

This will write "Hello" to both stdout and the logfile.

Milan
  • 1,743
  • 2
  • 13
  • 36
Amith Koujalgi
  • 10,746
  • 2
  • 18
  • 22
  • 1
    Hi Amith,I do not want to use this as it need manual interaction to do this (> file). Is there something which I can do from with in script as well or once execution has completed then what ever has came on console, takes and push to a file?? – user2033758 Feb 16 '13 at 04:31
  • @user2033758: Once it is out of your program and on the console once execution is complete, the program does not have any control over it anymore. – Anthon Mar 13 '13 at 03:49
  • 1
    @user2033758: There are two suggestions in this answer, and the second does not need any manual interaction. You can either use the command line or you can use the class in your code. I tested this and the class sends output to both console and file, without any special parameters on the command line. – JDM May 04 '13 at 10:49
  • 1
    How do I get the normal `sys.stdout` behavior back? Say I want this, but only in the startup phase of my program, and then after a while I want to just let the program run, but without saving everything – Toke Faurby Jan 30 '17 at 19:38
  • 1
    @TokeFaurbyYou could save the previous `sys.stdout` in Logger, and create a function which reverts it to the original state. – Gábor Fekete May 08 '17 at 08:07
  • Doesn't answer the OP question. How to direct to +both+ screen and file. – Kevin Buchs Aug 19 '17 at 18:21
  • I added a 0 parameter when opening the logfile to make it unbuffered, so that output is written immediately to both stdout and the logfile. – user258279 Apr 22 '18 at 07:37
  • this doesnt write any errors of Python – Umair Ayub Feb 24 '22 at 05:59
  • and how do I write stdout and stderr to two different files? is there some syntax like python foo_bar.py > file1 > file2? – Moritz Aug 19 '22 at 13:46
  • 2
    @UmairAyub If you also want python error logging just add `sys.stderr = sys.stdout` after `sys.stdout = Logger()` – insaneinvader Apr 06 '23 at 19:38
  • @insaneinvader If I have os.system("python test.py") in my python script, how can I output the print statement in test.py to logfile.log? – David Wei Apr 28 '23 at 09:07
25

I got the way to redirect the out put to console as well as to a text file as well simultaneously:

te = open('log.txt','w')  # File where you need to keep the logs

class Unbuffered:

   def __init__(self, stream):

       self.stream = stream

   def write(self, data):

       self.stream.write(data)
       self.stream.flush()
       te.write(data)    # Write the data of stdout here to a text file as well



sys.stdout=Unbuffered(sys.stdout)
Anthon
  • 69,918
  • 32
  • 186
  • 246
user2033758
  • 1,848
  • 3
  • 16
  • 16
  • 1
    I ran a python process and unable to see the data in `log.txt` file unless until i kill the process. Looks like log is getting buffered and then dumped at the end. – Mukesh Kumar Jan 21 '21 at 09:34
  • 3
    Hello @MukeshKumar, I to faced the same problem. I added `te.flush()` to the end of file to force it to stop buffer the data and write it. Also, I had problems deleting the data because it was still opened in the application, so I added `te.close()`. As a third problem, my cell in Jupyter kept running indefinitely until I kill the kernel. I solved it by capturing the original state of stdout (`orig_stdout = sys.stdout`) and assigned it again in the end of script (`sys.stdout = orig_stdout`). After that, everything was running very well. – Arnold Souza Jan 25 '21 at 17:56
  • Hi @ArnoldSouza Thanks for the reply. How does `te.flush()` works ? when does it flush the data to the log file ? is it based in time or buffer size ? can you please share your code as well in case i am missing something – Mukesh Kumar Jan 26 '21 at 12:55
  • I posted the code as an answer in this stack. About obj.flush(), I believe the function `open` works based in buffer size. More about it here: https://stackoverflow.com/questions/3167494/how-often-does-python-flush-to-a-file – Arnold Souza Jan 26 '21 at 18:50
  • Hi Arnold, may I know, if I were to stop the streaming capture, do I add these commands at the end of the script? te.flush\ t.close()\ sys.stdout = orig_stdout? My jupyter is still running indefinitely. – user3782604 Sep 05 '21 at 04:28
18

Use logging module to debug and follow your app

Here is how I managed to log to file and to console / stdout

import logging
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s',
                    filename='logs_file',
                    filemode='w')
# Until here logs only to file: 'logs_file'

# define a new Handler to log to console as well
console = logging.StreamHandler()
# optional, set the logging level
console.setLevel(logging.INFO)
# set a format which is the same for console use
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# tell the handler to use this format
console.setFormatter(formatter)
# add the handler to the root logger
logging.getLogger('').addHandler(console)

# Now, we can log to both ti file and console
logging.info('Jackdaws love my big sphinx of quartz.')
logging.info('Hello world')

read it from source: https://docs.python.org/2/howto/logging-cookbook.html

Radu Gabriel
  • 2,841
  • 23
  • 15
  • Not cool to copy-paste others-own answers ttps://stackoverflow.com/a/9321890/7646049 just give the reference on them. – Evgeny Kuznetsov Jan 12 '21 at 18:38
  • 2
    @EvgenyKuznetsov I posted a link to the python docs, which is where I copied from; Have you thought that the other stackoverflow answer maybe copied from the same place? – Radu Gabriel Jan 14 '21 at 11:35
14

Based on Amith Koujalgi's answer, here's a simple module you can use for logging -

transcript.py:

"""
Transcript - direct print output to a file, in addition to terminal.

Usage:
    import transcript
    transcript.start('logfile.log')
    print("inside file")
    transcript.stop()
    print("outside file")
"""

import sys

class Transcript(object):

    def __init__(self, filename):
        self.terminal = sys.stdout
        self.logfile = open(filename, "a")

    def write(self, message):
        self.terminal.write(message)
        self.logfile.write(message)

    def flush(self):
        # this flush method is needed for python 3 compatibility.
        # this handles the flush command by doing nothing.
        # you might want to specify some extra behavior here.
        pass

def start(filename):
    """Start transcript, appending print output to given filename"""
    sys.stdout = Transcript(filename)

def stop():
    """Stop transcript and return print functionality to normal"""
    sys.stdout.logfile.close()
    sys.stdout = sys.stdout.terminal
Community
  • 1
  • 1
Brian Burns
  • 20,575
  • 8
  • 83
  • 77
12
from IPython.utils.io import Tee
from contextlib import closing

print('This is not in the output file.')        

with closing(Tee("outputfile.log", "w", channel="stdout")) as outputstream:
    print('This is written to the output file and the console.')
    # raise Exception('The file "outputfile.log" is closed anyway.')
print('This is not written to the output file.')   

# Output on console:
# This is not in the output file.
# This is written to the output file and the console.
# This is not written to the output file.

# Content of file outputfile.txt:
# This is written to the output file and the console.

The Tee class in IPython.utils.io does what you want, but it lacks the __enter__ and __exit__ methods needed to call it in the with-statement. Those are added by contextlib.closing.

oha
  • 151
  • 1
  • 4
10

I devised an easier solution. Just define a function that will print to file or to screen or to both of them. In the example below I allow the user to input the outputfile name as an argument but that is not mandatory:

OutputFile= args.Output_File
OF = open(OutputFile, 'w')

def printing(text):
    print text
    if args.Output_File:
        OF.write(text + "\n")

After this, all that is needed to print a line both to file and/or screen is: printing(Line_to_be_printed)

hector garcia
  • 109
  • 1
  • 2
  • 1
    Simple and brilliant idea to handle it with a custom function. – Akif Aug 31 '17 at 08:05
  • 2
    This works. However, note that the input 'text' to 'printing' function has to be explicit text. It won't support any additional arguments that print supports. – rrlamichhane Jul 25 '18 at 23:58
  • My issue for supporting non-explicit text (like datetime.datetime) was in the write function. I just surrounded text with `str(text)` to convert to string. – tzg Nov 28 '18 at 18:52
5

Here is a simple context manager that prints to the console and writes the same output to an file. It also writes any exceptions to the file.

import traceback
import sys

# Context manager that copies stdout and any exceptions to a log file
class Tee(object):
    def __init__(self, filename):
        self.file = open(filename, 'w')
        self.stdout = sys.stdout

    def __enter__(self):
        sys.stdout = self

    def __exit__(self, exc_type, exc_value, tb):
        sys.stdout = self.stdout
        if exc_type is not None:
            self.file.write(traceback.format_exc())
        self.file.close()

    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)

    def flush(self):
        self.file.flush()
        self.stdout.flush()

To use the context manager:

print("Print")
with Tee('test.txt'):
    print("Print+Write")
    raise Exception("Test")
print("Print")
supersolver
  • 426
  • 5
  • 14
  • Good answer! In Python3 I got an error because of "isatty" missing. To fix it add the following: def isatty(self): return False – matt3o Apr 25 '20 at 07:59
5

I've tried a few solutions here and didn't find the one that writes into file and into console at the same time. So here is what I did (based on this answer)

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout

    def write(self, message):
        with open ("logfile.log", "a", encoding = 'utf-8') as self.log:            
            self.log.write(message)
        self.terminal.write(message)

    def flush(self):
        #this flush method is needed for python 3 compatibility.
        #this handles the flush command by doing nothing.
        #you might want to specify some extra behavior here.
        pass
sys.stdout = Logger()   

This solution uses more computing power, but reliably saves all of the data from stdout into logger file and uses less memeory. For my needs I've added time stamp into self.log.write(message) aswell. Works great.

Sanon
  • 73
  • 1
  • 8
5

This way worked very well in my situation. I just added some modifications based on other code presented in this thread.

import sys, os 

orig_stdout = sys.stdout  # capture original state of stdout

te = open('log.txt','w')  # File where you need to keep the logs

class Unbuffered:
   def __init__(self, stream):
       self.stream = stream

   def write(self, data):
       self.stream.write(data)
       self.stream.flush()
       te.write(data)    # Write the data of stdout here to a text file as well

sys.stdout=Unbuffered(sys.stdout)




#######################################
##  Feel free to use print function  ##
#######################################

print("Here is an Example =)")

#######################################
##  Feel free to use print function  ##
#######################################




# Stop capturing printouts of the application from Windows CMD
sys.stdout = orig_stdout  # put back the original state of stdout
te.flush()  # forces python to write to file
te.close()  # closes the log file

# read all lines at once and capture it to the variable named sys_prints
with open('log.txt', 'r+') as file:
    sys_prints = file.readlines() 

# erase the file contents of log file
open('log.txt', 'w').close()
Arnold Souza
  • 601
  • 5
  • 16
3

The following works on Linux where tee is natively available. To also use it on Windows, either use unxutils or find another tee-like option. Run your script using,

python -u <YOUR_SCRIPT>.py | tee log.txt

This will print in the console as well as log to the file. Also, don't forget to use the -u flag otherwise, you won't see any output in the console.

Kyle F Hartzenberg
  • 2,567
  • 3
  • 6
  • 24
Atif Ali
  • 384
  • 4
  • 17
2

Based on Brian Burns edited answer, I created a single class that is easier to call:

class Logger(object):

    """
    Class to log output of the command line to a log file

    Usage:
    log = Logger('logfile.log')
    print("inside file")
    log.stop()
    print("outside file")
    log.start()
    print("inside again")
    log.stop()
    """

    def __init__(self, filename):
        self.filename = filename

    class Transcript:
        def __init__(self, filename):
            self.terminal = sys.stdout
            self.log = open(filename, "a")
        def __getattr__(self, attr):
            return getattr(self.terminal, attr)
        def write(self, message):
            self.terminal.write(message)
            self.log.write(message)
        def flush(self):
            pass

    def start(self):
        sys.stdout = self.Transcript(self.filename)

    def stop(self):
        sys.stdout.log.close()
        sys.stdout = sys.stdout.terminal
crazjo
  • 485
  • 1
  • 8
  • 20
1

To redirect output to a file and a terminal without modifying how your Python script is used outside, you could use pty.spawn(itself):

#!/usr/bin/env python
"""Redirect stdout to a file and a terminal inside a script."""
import os
import pty
import sys

def main():
    print('put your code here')

if __name__=="__main__":
    sentinel_option = '--dont-spawn'
    if sentinel_option not in sys.argv:
        # run itself copying output to the log file
        with open('script.log', 'wb') as log_file:
            def read(fd):
                data = os.read(fd, 1024)
                log_file.write(data)
                return data

            argv = [sys.executable] + sys.argv + [sentinel_option]
            rc = pty.spawn(argv, read)
    else:
        sys.argv.remove(sentinel_option)
        rc = main()
    sys.exit(rc)

If pty module is not available (on Windows) then you could replace it with teed_call() function that is more portable but it provides ordinary pipes instead of a pseudo-terminal -- it may change behaviour of some programs.

The advantage of pty.spawn and subprocess.Popen -based solutions over replacing sys.stdout with a file-like object is that they can capture the output at a file descriptor level e.g., if the script starts other processes that can also produce output on stdout/stderr. See my answer to the related question: Redirect stdout to a file in Python?

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
1

Based on @Arnold Suiza's answer, here is a function you can run once in the beginning and afterwards all will be immediately printed to stdout & file:

def print_to_file(filename):
    orig_stdout = sys.stdout  # capture original state of stdout

    class Unbuffered:
        def __init__(self, filename):
            self.stream = orig_stdout
            self.te = open(filename,'w')  # File where you need to keep the logs

        def write(self, data):
            self.stream.write(data)
            self.stream.flush()
            self.te.write(data)    # Write the data of stdout here to a text file as well
            self.te.flush()

    sys.stdout=Unbuffered(filename)

Now just run print_to_file('log.txt') at program start and you're good to go!

DankMasterDan
  • 1,900
  • 4
  • 23
  • 35
  • May I know how do we stop the process in juypter? It is always on processing (*) status. – user3782604 Sep 05 '21 at 04:16
  • `AttributeError: 'Unbuffered' object has no attribute 'flush'` – Robert Muil Mar 29 '22 at 11:50
  • addendum to the error I posted for anyone else hit by it: it appears this was a library calling the flush (I was using `click.echo()`) -> I just added an empty `flush()` method to `print_to_file()` to fix. – Robert Muil Mar 29 '22 at 13:06
0

The answer provided by user2033758 is concise; however, it does not work if the code involves libraries using sys.stdout methods such as sys.stdout.flush(). I slightly modified this answer by adding the flush() method which simply calls the sys.stdout's flush() method, and I also moved the file object into the Unbuffered class:

import sys
class Unbuffered(object):
    def __init__(self, stream, filepath):
        self.stream = stream
        self.file = open(filepath, 'w')  # File where you need to keep the logs
    def write(self, data):
        self.stream.write(data)
        self.stream.flush()
        self.file.write(data)    # Write the data of stdout here to a text file as well
    def flush(self):
        self.stream.flush()
sys.stdout=Unbuffered(sys.stdout, filepath='console_text.txt')
Ismet Sahin
  • 113
  • 8
-3

I tried this:

"""
Transcript - direct print output to a file, in addition to terminal.

Usage:
    import transcript
    transcript.start('logfile.log')
    print("inside file")
    transcript.stop()
    print("outside file")
"""

import sys

class Transcript(object):

    def __init__(self, filename):
        self.terminal = sys.stdout, sys.stderr
        self.logfile = open(filename, "a")

    def write(self, message):
        self.terminal.write(message)
        self.logfile.write(message)

    def flush(self):
        # this flush method is needed for python 3 compatibility.
        # this handles the flush command by doing nothing.
        # you might want to specify some extra behavior here.
        pass

def start(filename):
    """Start transcript, appending print output to given filename"""
    sys.stdout = Transcript(filename)

def stop():
    """Stop transcript and return print functionality to normal"""
    sys.stdout.logfile.close()
    sys.stdout = sys.stdout.terminal
    sys.stderr = sys.stderr.terminal
Arya McCarthy
  • 8,554
  • 4
  • 34
  • 56
fxs
  • 1
-6

you can redirect the output to a file by using >> python with print rint's "chevron" syntax as indicated in the docs

let see,

fp=open('test.log','a')   # take file  object reference 
print >> fp , "hello world"            #use file object with in print statement.
print >> fp , "every thing will redirect to file "
fp.close()    #close the file 

checkout the file test.log you will have the data and to print on console just use plain print statement .

neotam
  • 2,611
  • 1
  • 31
  • 53
  • 1
    `>>` is not the redirect operator, it's the shift operator. Change this, please. – nbro Jan 06 '15 at 21:19
  • This is valid in python2.7. >> can be used with print statement to redirect string to given file object. Please verify by trying my solution by yourself – neotam Oct 02 '19 at 10:55