2

I do a

python py_prog.py | java scalaProg.Pkg

The python program must fetch stuff from DB and pipe it to Scala program. We have centralized error monitoring for python BUT the scala program may silently fail. So if the Scala program fails, I want its stderr transferred to our monitoring system with a third program like this bash pseudocode:

(python py_prog.py | java scalaProg.Pkg) || python logging_program.py

(Writing logging_program and making it talk to our error monitoring is easy, setting up a similar system for the scala program is hard).

So how do I accomplish:

  1. when scalaProg fails, pipe its stderr to a third program
  2. handling IOError: [Errno 32] Broken pipe in py_prog when scalaProg fails
Jesvin Jose
  • 22,498
  • 32
  • 109
  • 202

3 Answers3

1

With bash you could use process subtitution and you would still see the output of java scalaProg.Pkg:

python py_prog.py | java scalaProg.Pkg 2> >(python logging_program.py)

Or you could place it on tee to see stderr on the terminal as well:

python py_prog.py | java scalaProg.Pkg 2> >(tee >(python logging_program.py))

If it's the shell that runs java scalaProg.Pkg that sends the error message you could encapsulate it in a subshell to get the error as well:

python py_prog.py | (java scalaProg.Pkg;) 2> >(tee >(python logging_program.py))

If you need get everything (both stdout and stderr from java scalaProg.Pkg do this:

python py_prog.py | java scalaProg.Pkg > >(tee >(python logging_program.py)) 2>&1

Or this:

python py_prog.py | (java scalaProg.Pkg;) > >(tee >(python logging_program.py)) 2>&1

If you want to get all stdout and stderr both from python py_prog.py and java scalaProg.Pkg do this:

{python py_prog.py | java scalaProg.Pkg;} > >(tee >(python logging_program.py)) 2>&1

Or this which includes the error that might be generated from the calling shell as well:

(python py_prog.py | java scalaProg.Pkg;) > >(tee >(python logging_program.py)) 2>&1

If you only want to get the stderr from the session then just use 2>:

(python py_prog.py | java scalaProg.Pkg;) 2> >(tee >(python logging_program.py))
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • Please explain the part where it checks for failure before redirecting it to `logging_program` – Jesvin Jose Aug 08 '13 at 07:22
  • Please note that scalaProg returns stderr at all times, but we need it only when it crashes, in which case we need to send it to logging_program – Jesvin Jose Aug 08 '13 at 07:26
  • @aitchnyu If you could only detect if `java scalaProg.Pkg` has already got a problem when it exits then you have to send the error output of it temporarily somewhere like on a buffer or a file. It's almost impossible if you want `python logging_program.py` to run and read its error messages after it's no longer running. If you want to have a temporary file solution it could be provided. Even when reading a named pipe, you can't continue or stop your running program unless something reads from it on the other end. – konsolebox Aug 08 '13 at 09:17
1

Tadeck's answer can get you most of the way there; but one big remaining problem is the broken-pipe error killing off py_prog.py.

It turns out that catching broken pipes for sys.stdout is tricky, because sometimes they occur during shutdown when it is too late.

If my_prog.py is relatively clean you can wrap it with some special boilerplate. Let's say, for illustration purposes, that it looks something like this trivial program:

$ cat badpipe.py
import sys

def main():
    for i in range(1000):
        print 'line', i
    return 0

if __name__ == '__main__':
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        sys.exit('\nInterrupted')

The code under the __name__ == '__main__' test at the end is—or has been, anyway—my usual boilerplate for my standalone python programs. It turns out I may need to change it, based on this answer.

Anyway, if I try running this with two "bad" cases, one that exits immediately and one that reads a bit and then exits, it behaves two different ways. First, pipe into "exit immediately":

$ python badpipe.py | (exit 0)
Traceback (most recent call last):
  File "badpipe.py", line 10, in <module>
    sys.exit(main())
  File "badpipe.py", line 5, in main
    print 'line', i
IOError: [Errno 32] Broken pipe

That's about what I expected. But:

$ python badpipe.py | head -1
line 0
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

Whoa! Freaky! :-)

It turns out I can get the freaky behavior (lost sys.stderr) with the head -1 to go away by tuning up my wrapper a bit. Instead of just sys.exit(main()), I need to invoke sys.stdout.flush() (ideally, maybe also sys.stderr.flush() too, but so far I have only tested this much) before calling sys.exit:

if __name__ == '__main__':
    try:
        ret = main()
    except KeyboardInterrupt:
        ret = '\nInterrupted'
    try:
        sys.stdout.flush()
    finally:
        sys.exit(ret)

With this in place, I can now reliably catch the outermost IOError and check for a broken-pipe case. Here's the final (more or less) version, including main again:

import errno, sys

def main():
    for i in range(1000):
        print 'line', i
    return 0

if __name__ == '__main__':
    ret = 0
    try:
        try:
            ret = main()
        except KeyboardInterrupt:
            ret = '\nInterrupted'
        finally:
            sys.stdout.flush()
    except IOError as err:
        if err.errno == errno.EPIPE:
            sys.stderr.write('caught pipe-based IO-error\n')
            ret = 123 # or whatever
        else:
            raise # some other I/O error; attempt to get a traceback
    finally:
        sys.exit(ret)

The sys.stderr.write after catching EPIPE, and altered ret value, are mainly for illustration—there's nothing special about 123. Also I don't know if the final raise works right, as I have not tested it.

Running this gives:

$ python badpipe.py | (exit 0)
caught pipe-based IO-error
$ python badpipe.py | (head -1)
line 0
caught pipe-based IO-error
$ 

(Note: this is all in python 2.7 but 3.2 behaves similarly.)


This is all well and good if you can modify py_prog.py, but what if you can't?

In that case, I'd suggest writing a wrapper script (in whatever language, Python will work fine). Have your wrapper script read all its stdin and copy (i.e., write) it all to stdout but check for (catch) the broken-pipe error. If and when that occurs, change strategy: read the rest of stdin and just throw it away, so that py_prog.py happily believes it managed to send everything to stdout and complete. You can even make it write it to a subprocess.Popen that runs your java scalaProg.pkg command and does all the desired special logging case for you.

You might want to write this wrapper even if you can modify py_prog.py, depending on exactly what you want to have happen.

python py_prog.py | python wrap_java_thing.py

(I'm not going to write the wrapper for you though :-) )


Incidentally, the lost sys.stderr thing is a Python bug, issue 11380. An easy way to provoke it: python -c 'print "foo\n"*10000' | head -1

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
0

Do the following: https://stackoverflow.com/a/2342841/548696

python py_prog.py | java scalaProg.Pkg 2>&1 >/dev/null | python logging_program.py

This removes anything from stdout and only stderr is passed to your Python script.

Does that work for you?

Community
  • 1
  • 1
Tadeck
  • 132,510
  • 28
  • 152
  • 198