package ctbrec.ui.tabs.logging; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.LinkedList; import java.util.stream.Collectors; import com.google.common.eventbus.Subscribe; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.classic.spi.StackTraceElementProxy; import ctbrec.event.EventBusHolder; import ctbrec.ui.controls.SearchBox; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.scene.control.Tab; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.BorderPane; public class LoggingTab extends Tab { private static final int HISTORY_LENGTH = 10_000; private SearchBox filter = new SearchBox(); private TableView table = new TableView<>(); private ObservableList history = FXCollections.observableList(Collections.synchronizedList(new LinkedList<>())); private ObservableList filteredEvents = FXCollections.observableArrayList(); private DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; private volatile boolean tabClosed = false; private Object eventBustSubscriber = new Object() { @Subscribe public void publishLoggingEevent(LoggingEvent event) { if (!tabClosed) { Platform.runLater(() -> { history.add(event); if (history.size() > HISTORY_LENGTH - 1) { history.remove(0); } filter(); }); } } }; private void filter() { filteredEvents.clear(); filteredEvents.addAll(history.stream().filter(evt -> { String q = filter.getText().toLowerCase(); return evt.getLevel().toString().toLowerCase().contains(q) || createLogMessage(evt).toLowerCase().contains(q); }).collect(Collectors.toList())); } public LoggingTab() { setText("Logging"); subscribeToEventBus(); table = new TableView<>(filteredEvents); int idx = 0; TableColumn level = createTableColumn("Level", 65, idx++); level.setCellValueFactory(cdf -> new SimpleStringProperty(cdf.getValue().getLevel().toString())); table.getColumns().add(level); TableColumn time = createTableColumn("Timestamp", 200, idx++); time.setCellValueFactory(cdf -> { Instant instant = Instant.ofEpochMilli(cdf.getValue().getTimeStamp()); return new SimpleStringProperty(instant.atZone(ZoneId.systemDefault()).format(timeFormatter)); }); table.getColumns().add(time); TableColumn msg = createTableColumn("Message", 2000, idx++); msg.setCellValueFactory(cdf -> new SimpleStringProperty(createLogMessage(cdf.getValue()))); table.getColumns().add(msg); BorderPane layout = new BorderPane(); BorderPane.setMargin(table, new Insets(10)); BorderPane.setMargin(filter, new Insets(10, 10, 0, 10)); layout.setCenter(table); layout.setTop(filter); setContent(layout); setOnClosed(evt -> { EventBusHolder.BUS.unregister(eventBustSubscriber); tabClosed = true; }); filter.setPromptText("Search"); filter.textProperty().addListener( (observableValue, oldValue, newValue) -> filter()); } private String createLogMessage(LoggingEvent evt) { StringBuilder sb = new StringBuilder(evt.getFormattedMessage()); if(evt.getThrowableProxy() != null) { IThrowableProxy throwableProxy = evt.getThrowableProxy(); sb.append('\n').append(throwableProxy.getClassName()).append(':').append(' ').append(throwableProxy.getMessage()); for (StackTraceElementProxy step : throwableProxy.getStackTraceElementProxyArray()) { sb.append('\n').append('\t').append(step.getSTEAsString()); } } return sb.toString(); } private TableColumn createTableColumn(String text, int width, int idx) { TableColumn tc = new TableColumn<>(text); tc.setPrefWidth(width); tc.setUserData(idx); return tc; } private void subscribeToEventBus() { EventBusHolder.BUS.register(eventBustSubscriber); } }