Search code examples
pythonsubprocesstornado

call xtail by tornado.proces.Subprocess


How can I call xtail by tornado.proces.Subprocess?

import subprocess

from tornado.ioloop import IOLoop
from tornado import gen
from tornado import process

class Reader(object):
    def __init__(self, xwatch_path, max_idle=600, ioloop=None):
        self.xwatch_path = xwatch_path
        self.ioloop = ioloop
        self.max_idle = max_idle

    @gen.coroutine
    def call_subprocess(self, cmd, stdin_data=None, stdin_async=False):
        stdin = STREAM if stdin_async else subprocess.PIPE
        sub_process = process.Subprocess(
            cmd, stdin=stdin, stdout=STREAM, stderr=STREAM, io_loop=self.ioloop
        )

        if stdin_data:
            if stdin_async:
                yield gen.Task(sub_process.stdin.write, stdin_data)
            else:
                sub_process.stdin.write(stdin_data)
        if stdin_async or stdin_data:
            sub_process.stdin.close()
        result, error = yield [
            gen.Task(sub_process.stdout.read_until, '\n'),
            gen.Task(sub_process.stderr.read_until, '\n')
        ]
        print result
        raise gen.Return((result, error))

    @gen.coroutine
    def popen(self):
        while True:
            result, error = yield self.call_subprocess(['xtail', self.xwatch_path])

            print result, error

def read_log(ioloop):
    access_reader = AccessLogReader(
        '/home/vagrant/logs')


    ioloop.add_callback(access_reader.popen)


def main():
    ioloop = IOLoop.instance()
    read_log(ioloop)
    ioloop.start()


if __name__ == '__main__':
    main()

I would like to collect a few of the log changes in the log folder, ready to use xtail multiple folders to collect logs, and then I develop the environment for debugging.

I use Vim to modify the ~/log/123.txt file, but I can't see the output.


Solution

  • The statement

    result, error = yield [
        gen.Task(sub_process.stdout.read_until, '\n'),
        gen.Task(sub_process.stderr.read_until, '\n')
    ]
    

    reads one line of the process's standard output and one line of standard error, and blocks until it has read both lines. If xtail only writes to one of the two streams, this will never complete.

    You probably want to read in a loop (note that gen.Task is not necessary):

    @gen.coroutine
    def read_from_stream(stream):
        try:
            while True:
                line = yield stream.read_until('\n')
                print(line)
        except StreamClosedError:
            return
    

    If you care about the difference between stdout and stderr, read from them separately. This will print lines from each stream as they arrive, and stop when both streams are closed:

    yield [read_from_stream(sub_process.stdout), read_from_stream(sub_process.stderr)]
    

    If you don't, merge them by passing stdout=STREAM, stderr=subprocess.STDOUT when creating the subprocess, and only read from sub_process.stdout.