Has anyone played with NIO pipes to filter / intercept System.out?
Asked Answered
O

1

1

As suggested here I would like to do that inside the selector loop. What I would really want is to read contents written to system out inside my selector loop.

EDIT1: I coded a complete solution just to find out that you CANNOT redirect GC logs by using System.setOut. It simply goes straight to the FD or something. Show stopper! Unless I redirect to a file and pipe this file into my selector. Lots of work! See here.

Ockham answered 24/3, 2012 at 15:9 Comment(2)
Do you want to redirect System.out output to your file? Or only from your loop? The solution has been mentioned here - original answerGodhood
Just shows that the gc runs deep inside the JVM and do not disturb the world as the Java program sees it.Melanite
F
1

One way to do it would be as follows:

  • create a subclass of OutputStream that redirects its output to a Pipe's sink channel
  • redirect System.out using this class: System.setOut(new PrintStream(new MyOutputStream(pipe));
  • register the pipe's source channel with a selector and get whatever was written to System.out in the selector loop, i.e. the source channel's correpsonding SelectionKey is selected as readable()

The following immplementation is a naive but working implementation, which simply redirects to System.err everything that is written to System.out:

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

public class SystemOutPipe extends Thread {

  public static void main(String[] args)
  {
    try {
      SystemOutPipe sop = new SystemOutPipe();
      sop.start();
      System.out.println("This message should be redirected to System.err\nNow waiting 5 seconds ...");
      Thread.sleep(5000L);
      sop.setStopped(true);
      sop.join();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private Selector selector;
  private Pipe pipe;
  private boolean stopped = false;

  public SystemOutPipe() throws IOException {
    super("SystemOutPipe");
    pipe = Pipe.open();
    System.setOut(new PrintStream(new PipeOutputStream(pipe)));
    selector = Selector.open();
    pipe.source().configureBlocking(false);
    pipe.source().register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
  }

  @Override
  public void run() {
    try {
      while (!isStopped()) {
        int n = selector.select(1L);
        if (n > 0) {
          Iterator<SelectionKey> it = selector.selectedKeys().iterator();
          while (it.hasNext()) {
            SelectionKey key = it.next();
            it.remove();
            if (key.isReadable()) {
              new ReadHandler(key).run();
            }
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace(); // writes to System.err !
    }
  }

  public synchronized boolean isStopped() {
    return stopped;
  }

  public synchronized void setStopped(final boolean stopped) {
    this.stopped = stopped;
  }

  public class ReadHandler implements Runnable {
    private final SelectionKey key;

    public ReadHandler(final SelectionKey key) {
      this.key = key;
    }

    @Override
    public void run() {
      ByteBuffer bbuf = (ByteBuffer) key.attachment();
      ReadableByteChannel channel = (ReadableByteChannel) key.channel();
      try
      {
        int count = 0;
        do {
          bbuf.clear();
          count = channel.read(bbuf);
          if (count > 0) System.err.write(bbuf.array(), 0, count);
        } while(count > 0);
      } catch (IOException e) {
        e.printStackTrace();
        key.cancel();
      }
    }
  }

  public class PipeOutputStream extends OutputStream {
    private final Pipe pipe;

    public PipeOutputStream(final Pipe pipe) {
      this.pipe = pipe;
    }

    @Override
    public void write(final int b) throws IOException {
      write(new byte[] { (byte) b });
    }

    @Override
    public void write(final byte[] b) throws IOException {
      write(b, 0, b.length);
    }

    @Override
    public void write(final byte[] b, final int off, final int len) throws IOException {
      ByteBuffer bbuf = ByteBuffer.wrap(b, off, len);
      bbuf.position(len);
      bbuf.flip();
      int count = 0;
      while (count < len) {
        int n = pipe.sink().write(bbuf);
        if (n == 0) {
          // let's wait a bit and not consume cpu
          try {
            Thread.sleep(1L);
          } catch (InterruptedException e) {
            throw new IOException(e);
          }
        }
        else count += n;
      }
    }
  }
}
Forelimb answered 24/3, 2012 at 20:4 Comment(7)
For some nasty reason the verbose:gc on my mac is NOT going through System.out. That's kind of expected, since the GC verbose is probably not using System.out.println. It is most likely going straight to the FileDescriptor of the sysout. Now I am stuck after coding the whole thing. :( :(Ockham
See EDIT in the original question.Ockham
I have done what you did above but I keep getting Broken Pipe. Tried everything but not working. How to debug a broken pipe exception?Ockham
Any help here will be appreciated: #9858389Ockham
Do you have a stack trace you can provide? Which OS are you testing on? I've only tested this code on Windows so far, is it the same for you?Forelimb
For sanity's sake I also tested on Linux and it's still working for me. Are you sure the "broken pipe" is occurring on the Pipe, or could it be on another channel you read from, to write to the Pipe's sink?Forelimb
I added the exception there: #9858389Ockham

© 2022 - 2024 — McMap. All rights reserved.