Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redirecting SLF4J log to TextArea in JavaFX

I would like to show errors logged by SLF4J in TextArea in JavaFX. What I have so far is an appender in logback-test.xml:

<appender name="err" class="logtest.AppTA">
    <filter class="logtest.ErrFilter" />
    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>

TextArea ready to receive stream:

public class Output extends OutputStream{
    private final TextArea ta;
    public Output(TextArea ta) {
        this.ta = ta;
    }
    @Override
    public void write(int b) throws IOException {
        if (ta!=null) {
            ta.appendText(String.valueOf((char) b));
        }
    }
}

and a class to handle appending:

public class AppTA extends AppenderBase<ILoggingEvent> {

    PatternLayoutEncoder encoder;
    OutputStream os;

    @Override
    protected void append(ILoggingEvent event) {
        try {
            if (isEncoderInitialized) {
                this.encoder.doEncode(event);
            }
        } catch (IOException e) {
        }
    }

    @Override
    public void start() {
        if (this.encoder == null) {
            addError("No encoder set for the appender named [" + name + "].");
            return;
        }
        try {
            encoder.init(os);
        } catch (IOException ex) {
            Logger.getLogger(AppTA.class.getName()).log(Level.SEVERE, null, ex);
        }
        super.start();
    }

    public PatternLayoutEncoder getEncoder() {
        return encoder;
    }

    public void setEncoder(PatternLayoutEncoder encoder) {
        this.encoder = encoder;
    }
}

Now the problem I'm having is that my TextArea is in the controller class and I don't know how to link them together. Especially when SLF4J creates AppTA instance on its own - I don't really have a way to pass my TextArea to AppTA used by logger.

How can I tackle this?

like image 724
alex Avatar asked Aug 31 '16 11:08

alex


2 Answers

This answer is dependent on your underlying logging framework being logback. Since SLF4J only supports one logging implementation at a time I don't think its possible to solve in an implementation agnostic way.

The simplest way to solve this issue is to create your own appender and utilize static state so you can access the streams across the application.
Below I demonstrate a basic example of an appender that can have its output stream set statically at any time.
This has some limitations, mainly that it can only handle one outputstream at a time, but it shouldn't be too difficult to extend to support multiple.

In the below application when you click the log button it will log info and error messages, all output will go to stdout but only error messsages will be shown in the text area.

Basic JavaFx application class

public class Main extends Application {

    private static final Logger LOG = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        Button btn = new Button();
        btn.setText("Log stuff");
        btn.setOnAction(a-> {
            LOG.info("This is some info");
            LOG.error("This is some error");
        });

        TextArea textArea = new TextArea();
        OutputStream os = new TextAreaOutputStream(textArea);

        MyStaticOutputStreamAppender.setStaticOutputStream(os);

        GridPane grid = new GridPane();
        grid.add(textArea, 0 ,0);
        grid.add(btn, 0, 1);
        primaryStage.setScene(new Scene(grid, 500, 250));
        primaryStage.show();
    }

    private static class TextAreaOutputStream extends OutputStream {

        private TextArea textArea;

        public TextAreaOutputStream(TextArea textArea) {
            this.textArea = textArea;
        }

        @Override
        public void write(int b) throws IOException {
            textArea.appendText(String.valueOf((char) b));
        }
    }
}

Simple custom appender class

public class MyStaticOutputStreamAppender<E> extends OutputStreamAppender<E> {


    private static final DelegatingOutputStream DELEGATING_OUTPUT_STREAM = new DelegatingOutputStream(null);

    @Override
    public void start() {
        setOutputStream(DELEGATING_OUTPUT_STREAM);
        super.start();
    }

    public static void setStaticOutputStream(OutputStream outputStream) {
        DELEGATING_OUTPUT_STREAM.setOutputStream(outputStream);
    }

    private static class DelegatingOutputStream extends FilterOutputStream {

        /**
         * Creates a delegating outputstream with a NO-OP delegate
         */
        public DelegatingOutputStream(OutputStream out){
            super(new OutputStream() {
                @Override
                public void write(int b) throws IOException {}
            });
        }

        void setOutputStream(OutputStream outputStream) {
            this.out = outputStream;
        }
    }

}

Logback config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="MyCustomAppender" class="example.MyStaticOutputStreamAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
        </encoder>
    </appender>

    <root>
        <appender-ref ref="STDOUT" />
        <appender-ref ref="MyCustomAppender" />
    </root>

</configuration>
like image 175
Magnus Avatar answered Sep 24 '22 03:09

Magnus


You could configure Logback to write to System.out and System.err.

Configure those streams (setOut, setErr) in your application to write to the textarea.

This solution should work with any SLF4J binding without code changes.

Basically you would implement a read-only JavaFX console.

Also have a look at the following answer: https://stackoverflow.com/a/9219837/506855

like image 34
Puce Avatar answered Sep 22 '22 03:09

Puce