Skip to content

Instantly share code, notes, and snippets.

@shnplr
Last active December 22, 2015 12:52
Show Gist options
  • Save shnplr/81a0716a0ee305e5fd98 to your computer and use it in GitHub Desktop.
Save shnplr/81a0716a0ee305e5fd98 to your computer and use it in GitHub Desktop.
JavaFX - log4j appender that logs to observable string property
package com.sample;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Layout;
import org.apache.log4j.spi.LoggingEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* An appender which logs to the UI thread. For example:
*
*<t>
* ConsoleController.groovy - display logs inside a TextArea
*
* ObservableAppender appender = org.apache.log4j.Logger.getLogger("mylogger.console").getAppender("appconsole")
* appender.addLogListener( { msg -> view.builder.wtextArea.appendText(msg) } )
*
* log4j.properties - define appender and logger config
*
* # create an appender for ui console
* log4j.appender.appconsole=com.sample.ObservableAppender
* log4j.appender.appconsole.layout=org.apache.log4j.PatternLayout
* log4j.appender.appconsole.layout.ConversionPattern=[%d] %-5p - %m%n
*
* # create a logger to send console output
* # e.g. LoggerFactory.getLogger("mylogger.console").info("a message")
* log4j.logger.mylogger.console=info,appconsole
* log4j.additivity.esbtoolsg.console=false
*
* # also send some apache log messages to the console...
* # log4j.logger.org.apache.commons.httpclient=warn,appconsole
*
</t>
*/
public class ObservableAppender extends AppenderSkeleton {
private final StringProperty value = new SimpleStringProperty();
// private String getValue() { return value.get() }
// private void setValue(String value) { value.set(value) }
// public final ReadOnlyStringProperty valueProperty() { return value }
// store listeners for removal
// impl: do not reference inside Appender.append method
private final Map<LogListener, ChangeListener> listeners = Collections.synchronizedMap(new HashMap<>());
private boolean configured;
/**
* Default constructor does nothing
*/
public ObservableAppender() {
}
public ObservableAppender(Layout layout) {
this.layout = layout;
}
public interface LogListener {
void log(String message);
}
/**
*
* @param listener
*/
public void addLogListener(final LogListener listener) {
if (listener != null) {
ChangeListener<String> changeListener = (observable, oldValue, newValue) -> {
try {
listener.log(newValue);
} catch (Exception e) {
// prevent overflow if log produces an exception
e.printStackTrace();
}
};
if (Platform.isFxApplicationThread()) {
addLogListenerImpl(listener, changeListener);
} else {
Platform.runLater(() -> addLogListenerImpl(listener, changeListener));
}
}
}
private void addLogListenerImpl(final LogListener listener, final ChangeListener changeListener) {
ChangeListener<String> oldListener = listeners.remove(listener);
if (oldListener != null) {
value.removeListener(oldListener);
}
value.addListener(changeListener);
listeners.put(listener, changeListener);
configured = true;
}
public void removeLogListener(final LogListener listener) {
if (listener != null) {
if (Platform.isFxApplicationThread()) {
removeLogListenerImpl(listener);
} else {
Platform.runLater(() -> removeLogListenerImpl(listener));
}
}
}
private void removeLogListenerImpl(final LogListener listener) {
ChangeListener<String> oldListener = listeners.remove(listener);
if (oldListener != null) value.removeListener(oldListener);
configured = (listeners.size() != 0);
}
// org.apache.log4jAppender
@Override
public void close() {
synchronized (listeners) {
for (ChangeListener<String> listener : listeners.values()) {
value.removeListener(listener);
}
listeners.clear();
}
configured = false;
}
private void updateValue(String value) {
if (Platform.isFxApplicationThread()) {
this.value.set(value);
} else {
Platform.runLater(() -> ObservableAppender.this.value.set(value));
}
}
@Override
protected void append(LoggingEvent event) {
if (configured) {
updateValue(layout.format(event));
if (layout.ignoresThrowable()) {
String[] lines = event.getThrowableStrRep();
if (lines != null) {
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(line);
sb.append(Layout.LINE_SEP);
}
updateValue(sb.toString());
}
}
}
}
@Override
public boolean requiresLayout() {
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment