36

I would like to log all the output of a Python script. I tried:

import sys log = [] class writer(object): def write(self, data): log.append(data) sys.stdout = writer() sys.stderr = writer() 

Now, if I "print 'something' " it gets logged. But if I make for instance some syntax error, say "print 'something# ", it wont get logged - it will go into the console instead.

How do I capture also the errors from Python interpreter?

I saw a possible solution here:

http://www.velocityreviews.com/forums/showpost.php?p=1868822&postcount=3

but the second example logs into /dev/null - this is not what I want. I would like to log it into a list like my example above or StringIO or such...

Also, preferably I don't want to create a subprocess (and read its stdout and stderr in separate thread).

2
  • Perhaps writer should implement writelines() as well. Do not forget sys.stderr.flush() Commented Dec 24, 2009 at 0:54
  • "print 'something#" shouldn't work as missing a single quotation mark. However, how do you check whether your print is being logged? You store stdout and stderr in a list. All your print results should go to the list. How do you print out the list content? Commented Jan 29, 2019 at 20:57

11 Answers 11

35

I have a piece of software I wrote for work that captures stderr to a file like so:

import sys sys.stderr = open('C:\\err.txt', 'w') 

so it's definitely possible.

I believe your problem is that you are creating two instances of writer.

Maybe something more like:

import sys class writer(object): log = [] def write(self, data): self.log.append(data) logger = writer() sys.stdout = logger sys.stderr = logger 
Sign up to request clarification or add additional context in comments.

3 Comments

In that case, just add a dummy flush method to your writer class: def flush(self): pass
user file handler as log handler, neat. How about multiple modules access the log list/file at the same time?
@Decula I have no problem with that actually. My main programs have a redirection of sys.stdout to a pyQt5 widget that can display text, and all print(...) from modules are redirected too without having to incorporate sys.stdout = something_else.
8

You can't do anything in Python code that can capture errors during the compilation of that same code. How could it? If the compiler can't finish compiling the code, it won't run the code, so your redirection hasn't even taken effect yet.

That's where your (undesired) subprocess comes in. You can write Python code that redirects the stdout, then invokes the Python interpreter to compile some other piece of code.

6 Comments

You are right, of course it can't do it. Sorry for the stupid question. Another solution would be to "exec" the script inside another script.
Not all code is compiled at once. import statements are one example.
Another use case (though not EcirH's): capturing stderr from a C-library called from python.
Python code isn't compiled. It's interpreted on-the-fly.
@EthanBierlein Python is indeed compiled, but to bytecode, not machine instructions. The point is that there is a compilation step, which is what produces SyntaxErrors. If a SyntaxError occurs, then the code will not run, so there is nothing you can do in the code to log syntax errors.
|
8

To route the output and errors from Windows, you can use the following code outside of your Python file:

python a.py 1> a.out 2>&1 

Source: https://support.microsoft.com/en-us/help/110930/redirecting-error-messages-from-command-prompt-stderr-stdout

Comments

7

I can't think of an easy way. The python process's standard error is living on a lower level than a python file object (C vs. python).

You could wrap the python script in a second python script and use subprocess.Popen. It's also possible you could pull some magic like this in a single script:

import os import subprocess import sys cat = subprocess.Popen("/bin/cat", stdin=subprocess.PIPE, stdout=subprocess.PIPE) os.close(sys.stderr.fileno()) os.dup2(cat.stdin.fileno(), sys.stderr.fileno()) 

And then use select.poll() to check cat.stdout regularly to find output.

Yes, that seems to work.

The problem I foresee is that most of the time, something printed to stderr by python indicates it's about to exit. The more usual way to handle this would be via exceptions.

---------Edit

Somehow I missed the os.pipe() function.

import os, sys r, w = os.pipe() os.close(sys.stderr.fileno()) os.dup2(w, sys.stderr.fileno()) 

Then read from r

Comments

6

Since python 3.5 you can use contextlib.redirect_stderr

with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow) 

1 Comment

And for those who want to get the output as a string: ` with io.StringIO() as buff: with contextlib.redirect_stderr(buff): help(pow) print(buff.getvalue()) `
5

For such a request, usually it would be much easier to do it in the OS instead of in Python.

For example, if you're going to run "a.py" and record all the messages it will generate into file "a.out", it would just be

python a.py 2>&1 > a.out

The first part 2>&1 redirects stderr to stdout (0: stdin, 1:stdout, 2:stderr), and the second redirects that to a file called a.out.

And as far as I know, this command works in Windows, Linux or MacOS! For other file redirection techniques, just search the os plus "file redirection"

Comments

1

I found this approach to redirecting stderr particularly helpful. Essentially, it is necessary to understand if your output is stdout or stderr. The difference? Stdout is any output posted by a shell command (think an 'ls' list) while sterr is any error output.

It may be that you want to take a shell commands output and redirect it to a log file only if it is normal output. Using ls as an example here, with an all files flag:

# Imports import sys import subprocess # Open file log = open("output.txt", "w+") # Declare command cmd = 'ls -a' # Run shell command piping to stdout result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) # Assuming utf-8 encoding txt = result.stdout.decode('utf-8') # Write and close file log.write(txt) log.close() 

If you wanted to make this an error log, you could do the same with stderr. It's exactly the same code as stdout with stderr in its place. This pipes an error messages that get sent to the console to the log. Doing so actually keeps it from flooding your terminal window as well!

Saw this was a post from a while ago, but figured this could save someone some time :)

Comments

0
import sys import tkinter # ******************************************** def mklistenconsswitch(*printf: callable) -> callable: def wrapper(*fcs: callable) -> callable: def newf(data): [prf(data) for prf in fcs] return newf stdoutw, stderrw = sys.stdout.write, sys.stderr.write funcs = [(wrapper(sys.stdout.write, *printf), wrapper(sys.stderr.write, *printf)), (stdoutw, stderrw)] def switch(): sys.stdout.write, sys.stderr.write = dummy = funcs[0] funcs[0] = funcs[1] funcs[1] = dummy return switch # ******************************************** def datasupplier(): i = 5.5 while i > 0: yield i i -= .5 def testloop(): print(supplier.__next__()) svvitch() root.after(500, testloop) root = tkinter.Tk() cons = tkinter.Text(root) cons.pack(fill='both', expand=True) supplier = datasupplier() svvitch = mklistenconsswitch(lambda text: cons.insert('end', text)) testloop() root.mainloop() 

Comments

0

Python will not execute your code if there is an error. But you can import your script in another script an catch exceptions. Example:

Script.py

print 'something# 

FinalScript.py

from importlib.machinery import SourceFileLoader try: SourceFileLoader("main", "<SCRIPT PATH>").load_module() except Exception as e: # Handle the exception here 

Comments

0

To add to Ned's answer, it is difficult to capture the errors on the fly during the compilation.

You can write several print statements in your script and you can stdout to a file, it will stop writing to the file when the error occurs. To debug the code you could check the last logged output and check your script after that point.


Something like this:

# Add to the beginning of the script execution(eg: if __name__ == "__main__":). from datetime import datetime dt = datetime.now() script_dir = os.path.dirname(os.path.abspath(__file__)) # gets the path of the script stdout_file = script_dir+r'\logs\log'+('').join(str(dt.date()).split("-"))+r'.log' sys.stdout = open(stdout_file, 'w') 

This will create a log file and stream the print statements to the file.


Note: Watch out for escape characters in your filepath while concatenating with script_dir in the second line from the last in the code. You might want something similar to raw string. You can check this thread for this.

Comments

0

I improve the answer using the logging module in order to control the file and the log

import logging class writer_info(object): def write(self, data): logging.info(data) class writer_error(object): def write(self, data): logging.info(data) sys.stdout = writer_info() sys.stderr = writer_error() 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.