Search code examples
pythonpopenstderrseek

Reading last error message in log file


In Python 2.7 I have the following code inside certain loop

file = open("log.txt", 'a+')
last_position = file.tell()
subprocess.Popen(["os_command_producing_error"], stderr = file)
file.seek(last_position)
error = file.read()
print(error) # example of some action with the error

The intention is that the error that was just given by stderr gets, say printed, while file is keeping the whole record.

I am a beginner in Python and I am not clear what happens in the stderr = file.

My problem is that error keeps being empty, even though errors keep getting logged in the file.

Could someone explain why?

I have tried adding closing and opening the file again, or file.flush() right after the subprocess line. But still the same effect.

Edit: The code in the answer below makes sense to me and it seems to work for for the author of that post. For me (in Windows) it is not working. It gives an empty err and an empty file log.txt. If I run it line by line (e.g. debugging) it does work. How to understand and solve this problem?

Edit: I changed the Popen with call and now it works. I guess call waits for the subprocess to finish in order to continue with the script.


Solution

  • error is empty because you are reading too soon before the process has a chance to write anything to the file. Popen() starts a new process; it does not wait for it to finish.

    call() is equivalent to Popen().wait() that does wait for the child process to exit that is why you should see non-empty error in this case (if the subprocess does write anything to stderr).

    #!/usr/bin/env python
    import subprocess
    
    with open("log.txt", 'a+') as file:
       subprocess.check_call(["os_command_producing_error"], stderr=file)
       error = file.read()
    print(error)
    

    You should be careful with mixing buffered (.read()) and unbuffered I/O (subprocess).

    You don't need the external file here, to read the error:

    #!/usr/bin/env python
    import subprocess
    
    error = subprocess.check_output(["os_command_producing_error"],
                                    stderr=subprocess.STDOUT)
    print(error)
    

    It merges stderr and stdout and returns the output.

    If you don't want to capture stdout then to get only stderr, you could use Popen.communicate():

    #!/usr/bin/env python
    import subprocess
    
    p = subprocess.Popen(["os_command_producing_error"], stderr=subprocess.PIPE)
    error = p.communicate()[1]
    print(error)
    

    You could both capture stderr and append it to a file:

    #!/usr/bin/env python
    import subprocess
    
    error = bytearray()
    p = subprocess.Popen(["os_command_producing_error"],
                         stderr=subprocess.PIPE, bufsize=1)
    with p.stderr as pipe, open('log.txt', 'ab') as file:
        for line in iter(pipe.readline, b''):
            error += line
            file.write(line)
    p.wait()
    print(error)
    

    See Python: read streaming input from subprocess.communicate().