sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/03: Provide details about the log record, in particular the stack trace.
Date Thu, 09 Jul 2020 12:47:18 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 863f5788c134346180a8cd398dfb59c9b55147d2
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Jul 9 10:10:28 2020 +0200

    Provide details about the log record, in particular the stack trace.
---
 .../java/org/apache/sis/gui/dataset/LogViewer.java | 215 ++++++++++++++++++---
 .../apache/sis/internal/gui/ExceptionReporter.java |   5 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 5 files changed, 195 insertions(+), 32 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/LogViewer.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/LogViewer.java
index b28cc65..60cdc8b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/LogViewer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/LogViewer.java
@@ -16,17 +16,35 @@
  */
 package org.apache.sis.gui.dataset;
 
+import java.text.DateFormat;
+import java.util.Date;
 import java.util.Locale;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.SimpleFormatter;
+import javafx.geometry.Insets;
+import javafx.geometry.Orientation;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+import javafx.scene.layout.VBox;
 import javafx.scene.layout.Region;
+import javafx.scene.layout.GridPane;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.control.SplitPane;
 import javafx.scene.control.TableView;
 import javafx.scene.control.TableColumn;
 import javafx.scene.control.TableColumn.CellDataFeatures;
+import javafx.scene.control.TitledPane;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.control.ToggleGroup;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ReadOnlyBooleanProperty;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
+import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.value.ObservableValue;
@@ -36,8 +54,11 @@ import javafx.collections.ObservableList;
 import org.apache.sis.gui.Widget;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.internal.gui.Styles;
 import org.apache.sis.internal.gui.LogHandler;
+import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.ImmutableObjectProperty;
+import org.apache.sis.util.CharSequences;
 
 
 /**
@@ -50,11 +71,36 @@ import org.apache.sis.internal.gui.ImmutableObjectProperty;
  */
 public class LogViewer extends Widget {
     /**
+     * Localized string representations of {@link Level}.
+     * This map shall be read and written from JavaFX thread only.
+     *
+     * @see #toString(Level)
+     */
+    private static final Map<Level,String> LEVEL_NAMES = new HashMap<>();
+
+    /**
      * The table of log records.
      */
     private final TableView<LogRecord> table;
 
     /**
+     * The view combining the table with details about the selected record.
+     *
+     * @see #getView()
+     */
+    private final SplitPane view;
+
+    /**
+     * Details about selected record.
+     */
+    private final Label level, time, logger, classe, method;
+
+    /**
+     * Area where to show the log message.
+     */
+    private final TextArea message;
+
+    /**
      * The data store or resource for which to show log records.
      * If this property value is {@code null}, then the system logs will be shown
      * if {@link #systemLogs} is {@code true}, or no logs will be shown otherwise.
@@ -73,7 +119,7 @@ public class LogViewer extends Widget {
      *
      * @see #isEmptyProperty()
      */
-    private final Listener isEmpty;
+    private final IsEmpty isEmpty;
 
     /**
      * Whether {@link #source} is modified in reaction to a {@link #systemLogs} change, or
conversely.
@@ -86,6 +132,17 @@ public class LogViewer extends Widget {
     private final SimpleFormatter formatter;
 
     /**
+     * Format for dates and times using a short or long representation. The short representation
is for
+     * a column in the table, and the long representation is for the details panel below
the table.
+     */
+    private final DateFormat shortDates, longDates;
+
+    /**
+     * The button for showing the main message or the stack trace.
+     */
+    private final ToggleButton messageButton, traceButton;
+
+    /**
      * Creates an initially empty viewer of log records. For viewing logs, {@link #source}
      * must be set to a non-null value or {@link #systemLogs} must be set to {@code true}.
      */
@@ -97,35 +154,52 @@ public class LogViewer extends Widget {
      * Creates a new view of log records.
      */
     LogViewer(final Vocabulary vocabulary) {
-        formatter  = new SimpleFormatter();
         source     = new SimpleObjectProperty<>(this, "source");
         systemLogs = new SimpleBooleanProperty (this, "systemLogs");
-        isEmpty    = new Listener(this);
+        isEmpty    = new IsEmpty(this);
+        formatter  = new SimpleFormatter();
+        shortDates = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, vocabulary.getLocale());
+        longDates  = DateFormat.getDateTimeInstance(DateFormat.LONG,  DateFormat.LONG,  vocabulary.getLocale());
         table      = new TableView<>(FXCollections.emptyObservableList());
-
-        final TableColumn<LogRecord, String> level   = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Level));
-        final TableColumn<LogRecord, String> time    = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.DateAndTime));
-        final TableColumn<LogRecord, String> logger  = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Logger));
-        final TableColumn<LogRecord, String> classe  = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Class));
-        final TableColumn<LogRecord, String> method  = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Method));
-        final TableColumn<LogRecord, String> message = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Message));
-
-        level  .setCellValueFactory((cell) -> toString(cell, Vocabulary.Keys.Level));
-        time   .setCellValueFactory((cell) -> toString(cell, Vocabulary.Keys.DateAndTime));
-        logger .setCellValueFactory((cell) -> toString(cell, Vocabulary.Keys.Logger));
-        classe .setCellValueFactory((cell) -> toString(cell, Vocabulary.Keys.Class));
-        method .setCellValueFactory((cell) -> toString(cell, Vocabulary.Keys.Method));
-        message.setCellValueFactory((cell) -> toString(cell, Vocabulary.Keys.Message));
-
-        level .setVisible(false);
-        time  .setVisible(false);
-        logger.setVisible(false);
-        classe.setVisible(false);
-        method.setVisible(false);
-
-        table.getColumns().setAll(level, time, logger, classe, method, message);
         table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
         table.setTableMenuButtonVisible(true);
+        table.getColumns().setAll(column(vocabulary, Vocabulary.Keys.Level),
+                                  column(vocabulary, Vocabulary.Keys.DateAndTime),
+                                  column(vocabulary, Vocabulary.Keys.Logger),
+                                  column(vocabulary, Vocabulary.Keys.Class),
+                                  column(vocabulary, Vocabulary.Keys.Method),
+                                  column(vocabulary, Vocabulary.Keys.Message));
+
+        final Font font = Font.font(null, FontWeight.SEMI_BOLD, -1);
+        final GridPane details = Styles.createControlGrid(0,
+                label(font, vocabulary, Vocabulary.Keys.Level,       level  = new Label()),
+                label(font, vocabulary, Vocabulary.Keys.DateAndTime, time   = new Label()),
+                label(font, vocabulary, Vocabulary.Keys.Logger,      logger = new Label()),
+                label(font, vocabulary, Vocabulary.Keys.Class,       classe = new Label()),
+                label(font, vocabulary, Vocabulary.Keys.Method,      method = new Label()));
+
+        messageButton = new ToggleButton(vocabulary.getString(Vocabulary.Keys.Message));
+        traceButton   = new ToggleButton(vocabulary.getString(Vocabulary.Keys.Trace));
+        messageButton.setSelected(true);
+        messageButton.setMaxWidth(Double.MAX_VALUE);
+        traceButton  .setMaxWidth(Double.MAX_VALUE);
+        final ToggleGroup buttonGroup = new ToggleGroup();
+        buttonGroup.getToggles().setAll(messageButton, traceButton);
+        final VBox textSelector = new VBox(6, messageButton, traceButton);
+        final Insets margin = new Insets(6, 0, 0, 0);
+
+        message = new TextArea();
+        message.setEditable(false);
+        GridPane.setConstraints(textSelector, 0, 5);
+        GridPane.setConstraints(message, 1, 5);
+        GridPane.setMargin(textSelector, margin);
+        GridPane.setMargin(message, margin);
+        details.getChildren().addAll(textSelector, message);
+        details.setVgap(0);
+
+        view = new SplitPane(table, new TitledPane(vocabulary.getString(Vocabulary.Keys.Details),
details));
+        view.setOrientation(Orientation.VERTICAL);
+        SplitPane.setResizableWithParent(details, false);
 
         source.addListener((p,o,n) -> {
             if (!isAdjusting) try {
@@ -145,6 +219,31 @@ public class LogViewer extends Widget {
                 isAdjusting = false;
             }
         });
+        final ReadOnlyObjectProperty<LogRecord> selected = table.getSelectionModel().selectedItemProperty();
+        buttonGroup.selectedToggleProperty().addListener((p,o,n) -> setMessageOrTrace(selected.get()));
+        selected.addListener((p,o,n) -> selected(n));
+    }
+
+    /**
+     * Creates a column and register its cell factory.
+     * This is a helper method for the constructor.
+     */
+    private TableColumn<LogRecord, String> column(final Vocabulary vocabulary, final
short key) {
+        final TableColumn<LogRecord, String> column = new TableColumn<>(vocabulary.getString(key));
+        column.setCellValueFactory((cell) -> toString(cell, key));
+        column.setVisible(key == Vocabulary.Keys.Message);
+        return column;
+    }
+
+    /**
+     * Creates a label of the "details" pane.
+     * This is a helper method for the constructor.
+     */
+    private static Label label(final Font font, final Vocabulary vocabulary, final short
key, final Label content) {
+        final Label label = new Label(vocabulary.getLabel(key));
+        label.setLabelFor(content);
+        label.setFont(font);
+        return label;
     }
 
     /**
@@ -163,11 +262,11 @@ public class LogViewer extends Widget {
      * Implementation of {@link LogViewer#isEmpty} property.
      * Also a listener for being notified when the property value needs to be changed.
      */
-    private static final class Listener extends ReadOnlyBooleanWrapper implements ListChangeListener<LogRecord>
{
+    private static final class IsEmpty extends ReadOnlyBooleanWrapper implements ListChangeListener<LogRecord>
{
         /**
          * Creates the {@link LogViewer#isEmpty} property.
          */
-        Listener(final LogViewer owner) {
+        IsEmpty(final LogViewer owner) {
             super(owner, "isEmpty", true);
         }
 
@@ -194,20 +293,31 @@ public class LogViewer extends Widget {
     }
 
     /**
+     * Returns the localized string representations of given {@link Level}.
+     */
+    private static String toString(final Level level) {
+        if (level == null) {
+            return null;
+        }
+        return LEVEL_NAMES.computeIfAbsent(level,
+                (v) -> CharSequences.upperCaseToSentence(v.getLocalizedName()).toString());
+    }
+
+    /**
      * Returns the string representation of a logger property for the given cell.
      */
-    private ObservableValue<String> toString(final CellDataFeatures<LogRecord,String>
cell, final int type) {
+    private ObservableValue<String> toString(final CellDataFeatures<LogRecord,String>
cell, final short type) {
         if (cell != null) {
             final LogRecord log = cell.getValue();
             if (log != null) {
                 String text;
                 switch (type) {
                     case Vocabulary.Keys.Level: {
-                        text = log.getLevel().getLocalizedName();
+                        text = toString(log.getLevel());
                         break;
                     }
                     case Vocabulary.Keys.DateAndTime: {
-                        text = log.getInstant().toString();
+                        text = shortDates.format(new Date(log.getMillis()));
                         break;
                     }
                     case Vocabulary.Keys.Logger: {
@@ -240,11 +350,54 @@ public class LogViewer extends Widget {
     }
 
     /**
+     * Invoked when a log record is selected.
+     */
+    private void selected(final LogRecord log) {
+        String level = null, time = null, logger = null, classe = null, method = null;
+        if (log != null) {
+            level   = toString(log.getLevel());
+            time    = longDates.format(new Date(log.getMillis()));
+            logger  = log.getLoggerName();
+            classe  = log.getSourceClassName();
+            method  = log.getSourceMethodName();
+            final boolean td = (log.getThrown() == null);
+            traceButton.setDisable(td);
+            if (td) {
+                messageButton.setSelected(true);
+            }
+        }
+        this.level  .setText(level);
+        this.time   .setText(time);
+        this.logger .setText(logger);
+        this.classe .setText(classe);
+        this.method .setText(method);
+        setMessageOrTrace(log);
+    }
+
+    /**
+     * Sets the text or the exception stack trace, depending which button is selected.
+     */
+    private void setMessageOrTrace(final LogRecord log) {
+        String text = null;
+        if (messageButton.isSelected()) {
+            message.setWrapText(true);
+            text = formatter.formatMessage(log);
+        } else if (traceButton.isSelected()) {
+            message.setWrapText(false);
+            final Throwable exception = log.getThrown();
+            if (exception != null) {
+                text = ExceptionReporter.getStackTrace(exception);
+            }
+        }
+        message.setText(text);
+    }
+
+    /**
      * Returns the control to show in the scene graph.
      * The implementation class may change in any future version.
      */
     @Override
     public Region getView() {
-        return table;
+        return view;
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
index d8902ad..2617a98 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
@@ -94,8 +94,11 @@ public final class ExceptionReporter {
 
     /**
      * Gets the stack trace of the given exception.
+     *
+     * @param  exception  the exception for which to get the stack trace.
+     * @return the stack trace.
      */
-    private static String getStackTrace(final Throwable exception) {
+    public static String getStackTrace(final Throwable exception) {
         final StringWriter buffer = new StringWriter();
         final PrintWriter  writer = new PrintWriter(buffer);
         exception.printStackTrace(writer);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 6a5b684..b4e39e4 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -1130,6 +1130,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short TopicCategory = 198;
 
         /**
+         * Trace
+         */
+        public static final short Trace = 245;
+
+        /**
          * Transformation
          */
         public static final short Transformation = 199;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index 5bf1e4c..ff8aa0e 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -229,6 +229,7 @@ Time                    = Time
 Time_1                  = {0} time
 Timezone                = Timezone
 TopicCategory           = Topic category
+Trace                   = Trace
 Transformation          = Transformation
 TransformationAccuracy  = Transformation accuracy
 Transparency            = Transparency
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index 29d24ea..bd84a72 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -236,6 +236,7 @@ Time                    = Temps
 Time_1                  = Heure {0}
 Timezone                = Fuseau horaire
 TopicCategory           = Cat\u00e9gorie th\u00e9matique
+Trace                   = Trace
 Transformation          = Transformation
 TransformationAccuracy  = Pr\u00e9cision de la transformation
 Transparency            = Transparence


Mime
View raw message