Dooggy
Dooggy

Reputation: 330

Output from Java program launched with Runtime.getRuntime().exec()

I'm trying to write on a log file the output or a Java program launched via Runtime.getRuntime().exec(). I'm using the following code.

public class Thread_Write extends Thread {
    int id;

    public void run() {
        try {
            File log = new File("./log"+id+".txt");
            log.createNewFile();
            FileWriter fw = new FileWriter("./"+"log"+id+".txt",true);
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec("java WriteToFile "+id);
            InputStream is = process.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String line;
            while ((line = br.readLine()) != null) {
                fw.write(line);
              }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Thread_Write(int i) {
        super();
        this.id = i;
    }

    public static void main(String[] args) {
        for (int i =0 ; i<10;i++) {
            (new Thread_Write(i)).start();
        }
    }
}

"java WriteToFile id" is suppose to write an exception on the terminal, but unfortunately, I'm not getting anything. What's odd is that if I change this command bye "echo test", "test" will be properly added at the end of the log file. Any idea why ?

Cheers.

Upvotes: 0

Views: 2111

Answers (3)

assylias
assylias

Reputation: 328568

The likeliest reason is that the exception goes to the error stream. One option is to redirect the error stream too: process.getErrorStream().

Alternatively you can use a ProcessBuilder:

ProcessBuilder pb = new ProcessBuilder("java", "WriteToFile", String.valueOf(id));
pb.redirectErrorStream(true);
pb.redirectOutput(log);
Process p = pb.start();

Upvotes: 2

Itay Maman
Itay Maman

Reputation: 30723

There are several issues here. Here's how I would write the run() method (rationale follows):

public void run() {
  try {
    Process process = Runtime.getRuntime().exec("java WriteToFile " + id);
    Thread err = consume(process.getErrorStream(), 
        new FileOutputStream("./" + "log" + id + ".txt"));
    Thread std = consume(process.getInputStream(), 
        new FileOutputStream("./" + "log_stdout" + id + ".txt"));

    err.join();
    std.join();

  } catch (Exception e) {
    e.printStackTrace();
  } 
}

private Thread consume(final InputStream stream, 
    final OutputStream out) {
  Thread result = new Thread() {
    public void run() {
      PrintWriter pw = new PrintWriter(out, true);
      try (Scanner sc = new Scanner(stream)) {
        while (sc.hasNext()) {
          pw.println(sc.nextLine());
        }
      }
    }
  };
  result.start();
  return result;
}

(1) Most importantly: if WriteToFile writes to both stderr and stdout, your process will block: as it only reads from stderr, there's no one to read the bytes off of the stdout buffer. Once this buffer fills up WriteToFile will block on the next println(). Assuming this happens before an exception was written both processes will be deadlocked, each one waiting for the other one to make the first move. The solution: spawn two threads, one for consuming stdout and the other for consuming stderr.

Other (minor) issues: (2) Instead of wrapping InputStream in an InputStreamReader which, in turn, is wrapped inside a BufferedReader you can use the Scanner class: its constructor can take an InputStream and it gives you the readLine() method that you need.

(3) No need to create the log variable if it sole purpose is to be used in log.createNewFile(); - the output file will be created for you by your FileWriter object.

(4) You'd probably want to use a PrintWriter object rather than a FileWriter. The former gives you println() which lets you print the output line-by-line.

Upvotes: 0

Petter
Petter

Reputation: 4165

Errors are not available through process.getInputStream(), you need to instead use process.getErrorStream(). An alternative is to first use processBuilder.redirectErrorStream(true). This will cause both stdout and stderr to be available on process.getInputStream().

Upvotes: 1

Related Questions