1

I have a GUI window with a JavaFX TextArea where I would like to output all logger output (info, debug, error, etc.) in real time (similar to the console output from an IDE when running the project).

I've had a look at this answer and I have tried to adapt the code but it wouldn't work, I have a feeling something does not get initialised or called or the logback.xml is not pointing to the correct class.

It is way too difficult to include all the code here so I have a demo project on GitHub, please clone and let me know what you think?

Logger: logback, slf4j, log4j

IDE used: IntelliJ 2018.1


package Demo_Logger;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.OutputStream;

import static Demo_Logger.controller.ControllerMain.CONTROLLER_MAIN;

public class Main extends Application {

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

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent parent = FXMLLoader.load(getClass().getResource("/view/main.fxml"));

        primaryStage.setTitle("Demo logger");
        primaryStage.setWidth(400);
        primaryStage.setHeight(400);
        primaryStage.setScene(new Scene(parent));
        primaryStage.show();

        Platform.runLater(this::setTextArea);

        Platform.runLater(this::logToTexArea);

        System.out.println("Main -> init"); //OK called
    }

    private void setTextArea() {
        OutputStream outputStream = new TextAreaOutputStream(CONTROLLER_MAIN.textArea);
        MyStaticOutputStreamAppender.setStaticOutputStream(outputStream);
    }

    private void logToTexArea() {
        logger.debug("Debug example");
        logger.error("Error example");

        CONTROLLER_MAIN.textArea.setText("ADD LOGGER OUTPUT HERE");
    }
}

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="Demo_Logger.controller.ControllerMain">

    <center>
        <HBox alignment="CENTER">
            <VBox alignment="CENTER" minWidth="100.0" spacing="10.0">
                <Button fx:id="btnLogInfo" mnemonicParsing="false" onAction="#btnLogInfo" text="Log Info" />
                <Button fx:id="btnLogError" mnemonicParsing="false" onAction="#btnLogError" text="Log Error" />
            </VBox>
            <TextArea fx:id="textArea" promptText="logger has to output here" />
        </HBox>
    </center>
</BorderPane>

package Demo_Logger.controller;

import Demo_Logger.Main;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ControllerMain {

    public static ControllerMain CONTROLLER_MAIN;

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

    @FXML
    public TextArea textArea;

    @FXML
    public Button btnLogInfo, btnLogError;


    public ControllerMain() {
        CONTROLLER_MAIN = this;
    }

    @FXML
    public void btnLogInfo() {
        logger.info("Button info click");
    }

    @FXML
    public void btnLogError() {
        logger.error("Button error click");
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                <!-- THE PATTERN IS NOT WORKING - INVESTIGATE -->
                %-50(%d{HH:mm:ss} [%level{}] %class{0}.%method{0}\(\)) - %message{}%n{}
            </Pattern>
        </layout>
    </appender>

    <!--<appender name="MyCustomAppender" class=".src.main.java.Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class="src.main.java.Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class=".main.java.Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class="main.java.Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class=".java.Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class="java.Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class=".Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class="Demo_Logger.MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class=".MyStaticOutputStreamAppender">-->
    <!--<appender name="MyCustomAppender" class="MyStaticOutputStreamAppender">-->

    <appender name="MyCustomAppender" class="Demo_Logger.MyStaticOutputStreamAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ALL</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>

package Demo_Logger;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public 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 {
            }
        });

        System.out.println("DelegatingOutputStream -> init"); //OK called
    }

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

        System.out.println("DelegatingOutputStream -> setOutputStream"); //OK called
    }
}

package Demo_Logger;

import javafx.scene.control.TextArea;

import java.io.IOException;
import java.io.OutputStream;

public class TextAreaOutputStream extends OutputStream {

    private TextArea textArea;

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

        System.out.println("TextAreaOutputStream -> init"); //OK called
    }

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

        System.out.println("TextAreaOutputStream -> write");
    }
}

package Demo_Logger;

import ch.qos.logback.core.OutputStreamAppender;

import java.io.OutputStream;

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);
        System.out.println("MyStaticOutputStreamAppender -> start");

        super.start();
    }

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

        System.out.println("MyStaticOutputStreamAppender -> setStaticOutputStream"); //OK called
    }
}

4673_j
  • 477
  • 1
  • 6
  • 20
  • I think adding a `Text` to a `TextArea` in JavaFX requires to be wrapped in `Platform.runLater()`. Please provide a code snippet that shows how you are trying to output log text. – deHaar May 30 '18 at 11:52
  • @deHaar I'm trying to use the **logback.xml** config file to use the class which redirects the `outputStream`, in this case the line is: `` – 4673_j May 30 '18 at 11:57
  • Then where exactly is your problem located? Are you able to redirect the output stream but you cannot display it or isn't the redirection working? Please show some code... – deHaar May 30 '18 at 12:00
  • Dear @deHaar, I don't know where the problem is, as in the description above I have mentioned what I suspect. I cannot paste here all the code as it will make this post unreadable, please clone the GitHub repository from the link given - it's a small project. – 4673_j May 30 '18 at 12:04
  • OK, time to show code then... I will take a look at your GitHub link but questions here on SO basically require code snippets or error messages of any form at least. **You have shared the binaries on GitHub, not your code** – deHaar May 30 '18 at 12:06
  • @deHaar there are no error messages or any indication that the stream is redirected. There is a **Clone** button, but if not then clone from: https://github.com/demo4673j/Demo_Logger.git – 4673_j May 30 '18 at 12:09
  • Sorry, I can't clone it at the moment. Would have to do that later the day! – deHaar May 30 '18 at 12:10
  • 2
    Create a [*minimal*, complete and verifiable](https://stackoverflow.com/help/mcve) example, not a pointer to github. This will increase the probability that people will try to help you. Asking them to clone a git repository will decrease the probability. I won't for example, and I created in the past a remote log viewer for Java in JavaFX. – M. le Rutte May 30 '18 at 12:19
  • Dear @M.leRutte I did precisely the _minimal_ part by creating the demo on GitHub, this is the whole point to be able to import the project in a few seconds and run it! If someone is trying to reproduce the code, it will have to copy the code from here anyway, so I took this one step further. Anyway, I believe you are right, no one will clone it, I will have place all the classes in the question.. – 4673_j May 30 '18 at 12:31
  • 3
    @4673_j I think you misunderstood the [MCVE] link. If you have a project that is too large to minimize directly, the idea is that you create a very small new project from scratch that does *only* what you are having specific trouble with. Then post that complete project here. You also need to be specific about what the issue is: all we have here is "It wouldn't work." It's impossible to answer this question with the information provided. – James_D May 30 '18 at 12:34

0 Answers0