Redirecting Console Streams

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Redirecting Console Streams

asotobu
Hi guys, I am trying to redirect the messages sent by Asciidoctor (Ruby) in console to AsciidoctorJ logging system. I have found some examples in the internet and I don't know why but it doesn't work. I have created a test class to isolate the problem:

public class Main {

    static Logger logger;
    static {
        logger = Logger.getLogger("xxx");
       
        FileHandler fh;
        try {
            fh = new FileHandler("./MyLogFile.log");
            logger.addHandler(fh);
            SimpleFormatter formatter = new SimpleFormatter();  
            fh.setFormatter(formatter);
        } catch (SecurityException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
         
        System.setOut(new PrintStream(new LoggingOutputStream(logger, Level.INFO)));
        System.setErr(new PrintStream(new LoggingOutputStream(logger)));
    }
   
    public static void main(String args[]) {
        System.out.println("Hello");
   
    }
   
    static class LoggingOutputStream extends ByteArrayOutputStream {

        private String lineSeparator;

        private Logger logger;
        private Level level;

        public LoggingOutputStream(Logger logger) {
            super();
            this.logger = logger;
            this.lineSeparator = System.getProperty("line.separator");
        }

        public LoggingOutputStream(Logger logger, Level level) {
            super();
            this.logger = logger;
            this.level = level;
            this.lineSeparator = System.getProperty("line.separator");
        }

        public void flush() throws IOException {

            String record;
            synchronized(this) {
                super.flush();
                record = this.toString();
                super.reset();

                if (record.length() == 0 || record.equals(this.lineSeparator)) {
                    // avoid empty records
                    return;
                }
                System.out.println("My Log: "+record);
                if(this.logger == null) {

                    if(record.contains("WARNING")) {
                        this.logger.logp(Level.WARNING, "Asciidoctor", "", record);
                    } else {
                        if(record.contains("FAILED")) {
                            this.logger.logp(Level.SEVERE, "Asciidoctor", "", record);
                        } else {
                            this.logger.logp(Level.INFO, "Asciidoctor", "", record);
                        }
                    }

                } else {
                    this.logger.logp(this.level, "", "", record);
                }
            }
        }
    }
   
}


I don't know why but flush method is never called (I thought that this method will be called by Logger) but it seems not. Any idea on how to implement this?
Reply | Threaded
Open this post in threaded view
|

Re: Redirecting Console Streams

abelsromero
Hi,

I'm not sure if this is the right approach but I found some things that explain why this may fail.

First, the `PrintStream` constructor used sets the attribute `autofush` to false, setting this to true using:
    System.setOut(new PrintStream(new LoggingOutputStream(logger, Level.INFO), true));
ensures that flush is invoked. This helps testing, but I think that a previous error is preventing the method from ever being called.

Secondly, and i think the real problem is that the test to crush because some recursive loops, so far I've seen that calling logger.log inside the flush method triggers to call it again-and-again until it crashes.

Also, a minor detail the logger can have a null level, making the last line crash sometimes.

Seems like playing with the logger is trickier than it seems :%
Reply | Threaded
Open this post in threaded view
|

Re: Redirecting Console Streams

asotobu
I have managed to run it correctly with the help of Romain. Note that what it misses is the \n:

public class Run {
   
    static Logger logger;
    static {
        logger = Logger.getLogger("Asciidoctor");
        LoggerOutputStream out = new LoggerOutputStream(logger);
        System.setOut(new PrintStream(out));
       
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try { // can log or not something depending logger is already closed or not, best is to call it manually at the end of main if possible
                    out.flush();
                } catch (final IOException e) {
                    // no-op
                }
            }
        });
       
    }
   
    public static void main(String[] args) {
        // final PrintStream original = System.out; // if both redirections are needed
        doWork();
    }

    private static void doWork() {
        System.out.println("test1");
        System.out.print("test2");
    }
   
    static class LoggerOutputStream extends OutputStream {
        private final StringBuilder builder = new StringBuilder();
        private Logger logger;
       
        public LoggerOutputStream(Logger logger) {
            this.logger = logger;
        }
       
        private boolean doLog() {
            synchronized(this) {
            if (builder.length() > 0) {
                logger.info(builder.toString());
                builder.setLength(0);
                return true;
            }
            return false;
            }
        }

        @Override
        public void write(int b) throws IOException {
            if (b == '\n') {
                if (!doLog()) {
                    logger.info("");
                }
            } else {
                builder.append((char) b);
            }
        }

        @Override
        public void flush() throws IOException {
            doLog();
        }

        @Override
        public void close() throws IOException {
            doLog();
        }
    };
}
Reply | Threaded
Open this post in threaded view
|

Re: Redirecting Console Streams

mojavelinux
Administrator
Alex,

Your approach is certainly a sound one, though it does point to how important it is to have a proper logging hook in Asciidoctor because we really don't want to have to do this long term. Then again, we kind of already know that it's an important thing to fix, but seeing this code removes all doubt in my mind of the importance.

I do want to document the other approach I had briefly suggested over Twitter. Instead of intercepting System.out and System.err across the whole JVM, you could isolate this to the Ruby environment by reassigning the $stdout and $stderr global variables in the JRuby runtime when you set it up. That way, you'll only trap the $stdout and $stderr channels that Ruby uses (mostly like the Asciidoctor gem).

You'd implement this by creating an adapter for the Logger that responds to the "puts" method and passes it to the Logger.

...but even reading what I just wrote makes it clear how insane it is that we don't have a logger interface in Asciidoctor that you can implement {1}. I'm going to schedule this change for 1.5.3 and see if we can get it done by then.

Cheers,

-Dan


On Fri, Dec 12, 2014 at 8:25 AM, asotobu [via Asciidoctor :: Discussion] <[hidden email]> wrote:
I have managed to run it correctly with the help of Romain. Note that what it misses is the \n:

public class Run {
   
    static Logger logger;
    static {
        logger = Logger.getLogger("Asciidoctor");
        LoggerOutputStream out = new LoggerOutputStream(logger);
        System.setOut(new PrintStream(out));
       
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try { // can log or not something depending logger is already closed or not, best is to call it manually at the end of main if possible
                    out.flush();
                } catch (final IOException e) {
                    // no-op
                }
            }
        });
       
    }
   
    public static void main(String[] args) {
        // final PrintStream original = System.out; // if both redirections are needed
        doWork();
    }

    private static void doWork() {
        System.out.println("test1");
        System.out.print("test2");
    }
   
    static class LoggerOutputStream extends OutputStream {
        private final StringBuilder builder = new StringBuilder();
        private Logger logger;
       
        public LoggerOutputStream(Logger logger) {
            this.logger = logger;
        }
       
        private boolean doLog() {
            synchronized(this) {
            if (builder.length() > 0) {
                logger.info(builder.toString());
                builder.setLength(0);
                return true;
            }
            return false;
            }
        }

        @Override
        public void write(int b) throws IOException {
            if (b == '\n') {
                if (!doLog()) {
                    logger.info("");
                }
            } else {
                builder.append((char) b);
            }
        }

        @Override
        public void flush() throws IOException {
            doLog();
        }

        @Override
        public void close() throws IOException {
            doLog();
        }
    };
}


If you reply to this email, your message will be added to the discussion below:
http://discuss.asciidoctor.org/Redirecting-Console-Streams-tp2530p2532.html
To start a new topic under Asciidoctor :: Discussion, email [hidden email]
To unsubscribe from Asciidoctor :: Discussion, click here.
NAML


--