title: Logger output to TextArea in JavaFX
body: <p>I have a GUI window with a <code>JavaFX</code> <code>TextArea</code> 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).</p>
  
  <p>I've had a look at <a href="https://stackoverflow.com/questions/39247784/redirecting-slf4j-log-to-textarea-in-javafx?utm_medium=organic&amp;utm_source=google_rich_qa&amp;utm_campaign=google_rich_qa">this answer</a> 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 <code>logback.xml</code> is not pointing to the correct class.</p>
  
  <p>It is way too difficult to include all the code here so I have a demo project on <a href="https://github.com/demo4673j/Demo_Logger.git" rel="nofollow noreferrer">GitHub</a>, please clone and let me know what you think?</p>
  
  <p>Logger: <code>logback</code>, <code>slf4j</code>, <code>log4j</code></p>
  
  <p>IDE used: <code>IntelliJ 2018.1</code></p>
  
  <hr>
  
  <pre><code>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 -&gt; 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");
      }
  }
  </code></pre>
  
  <hr>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  
  &lt;?import javafx.scene.control.Button?&gt;
  &lt;?import javafx.scene.control.TextArea?&gt;
  &lt;?import javafx.scene.layout.BorderPane?&gt;
  &lt;?import javafx.scene.layout.HBox?&gt;
  &lt;?import javafx.scene.layout.VBox?&gt;
  &lt;BorderPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1"
              fx:controller="Demo_Logger.controller.ControllerMain"&gt;
  
      &lt;center&gt;
          &lt;HBox alignment="CENTER"&gt;
              &lt;VBox alignment="CENTER" minWidth="100.0" spacing="10.0"&gt;
                  &lt;Button fx:id="btnLogInfo" mnemonicParsing="false" onAction="#btnLogInfo" text="Log Info" /&gt;
                  &lt;Button fx:id="btnLogError" mnemonicParsing="false" onAction="#btnLogError" text="Log Error" /&gt;
              &lt;/VBox&gt;
              &lt;TextArea fx:id="textArea" promptText="logger has to output here" /&gt;
          &lt;/HBox&gt;
      &lt;/center&gt;
  &lt;/BorderPane&gt;
  </code></pre>
  
  <hr>
  
  <pre><code>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");
      }
  }
  </code></pre>
  
  <hr>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;configuration&gt;
  
      &lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
          &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
              &lt;Pattern&gt;
                  &lt;!-- THE PATTERN IS NOT WORKING - INVESTIGATE --&gt;
                  %-50(%d{HH:mm:ss} [%level{}] %class{0}.%method{0}\(\)) - %message{}%n{}
              &lt;/Pattern&gt;
          &lt;/layout&gt;
      &lt;/appender&gt;
  
      &lt;!--&lt;appender name="MyCustomAppender" class=".src.main.java.Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class="src.main.java.Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class=".main.java.Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class="main.java.Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class=".java.Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class="java.Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class=".Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class="Demo_Logger.MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class=".MyStaticOutputStreamAppender"&gt;--&gt;
      &lt;!--&lt;appender name="MyCustomAppender" class="MyStaticOutputStreamAppender"&gt;--&gt;
  
      &lt;appender name="MyCustomAppender" class="Demo_Logger.MyStaticOutputStreamAppender"&gt;
          &lt;filter class="ch.qos.logback.classic.filter.ThresholdFilter"&gt;
              &lt;level&gt;ALL&lt;/level&gt;
          &lt;/filter&gt;
          &lt;encoder&gt;
              &lt;pattern&gt;%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n&lt;/pattern&gt;
          &lt;/encoder&gt;
      &lt;/appender&gt;
  
      &lt;root&gt;
          &lt;appender-ref ref="STDOUT" /&gt;
          &lt;appender-ref ref="MyCustomAppender" /&gt;
      &lt;/root&gt;
  
  &lt;/configuration&gt;
  </code></pre>
  
  <hr>
  
  <pre><code>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 -&gt; init"); //OK called
      }
  
      void setOutputStream(OutputStream outputStream) {
          this.out = outputStream;
  
          System.out.println("DelegatingOutputStream -&gt; setOutputStream"); //OK called
      }
  }
  </code></pre>
  
  <hr>
  
  <pre><code>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 -&gt; init"); //OK called
      }
  
      @Override
      public void write(int b) throws IOException {
          textArea.appendText(String.valueOf((char) b));
  
          System.out.println("TextAreaOutputStream -&gt; write");
      }
  }
  </code></pre>
  
  <hr>
  
  <pre><code>package Demo_Logger;
  
  import ch.qos.logback.core.OutputStreamAppender;
  
  import java.io.OutputStream;
  
  public class MyStaticOutputStreamAppender&lt;E&gt; extends OutputStreamAppender&lt;E&gt; {
  
      private static final DelegatingOutputStream DELEGATING_OUTPUT_STREAM = new DelegatingOutputStream(null);
  
      @Override
      public void start() {
          setOutputStream(DELEGATING_OUTPUT_STREAM);
          System.out.println("MyStaticOutputStreamAppender -&gt; start");
  
          super.start();
      }
  
      static void setStaticOutputStream(OutputStream outputStream) {
          DELEGATING_OUTPUT_STREAM.setOutputStream(outputStream);
  
          System.out.println("MyStaticOutputStreamAppender -&gt; setStaticOutputStream"); //OK called
      }
  }
  </code></pre>
  
  <hr>