2014-06-09 3 views
8

나는이 같은 간단한 사용자 지정 로깅 프레임 워크이 프레임 워크 : 이제가장 효율적인 방법은

package something; 

import javafx.scene.control.TextArea; 

public class MyLogger { 
    public final TextArea textArea; 

    private boolean verboseMode = false; 
    private boolean debugMode = false; 

    public MyLogger(final TextArea textArea) { 
     this.textArea = textArea; 
    } 

    public MyLogger setVerboseMode(boolean value) { 
     verboseMode = value; 
     return this; 
    } 

    public MyLogger setDebugMode(boolean value) { 
     debugMode = value; 
     return this; 
    } 

    public boolean writeMessage(String msg) { 
     textArea.appendText(msg); 
     return true; 
    } 

    public boolean logMessage(String msg) { 
     return writeMessage(msg + "\n"); 
    } 

    public boolean logWarning(String msg) { 
     return writeMessage("Warning: " + msg + "\n"); 
    } 

    public boolean logError(String msg) { 
     return writeMessage("Error: " + msg + "\n"); 
    } 

    public boolean logVerbose(String msg) { 
     return verboseMode ? writeMessage(msg + "\n") : true; 
    } 

    public boolean logDebug(String msg) { 
     return debugMode ? writeMessage("[DEBUG] " + msg + "\n") : true; 
    } 
} 

가 무엇을 내가 원하는 그것을 확장하는 것이다는 것이다 그래야 스레드를 통한 메시지 로깅을 적절히 처리 할 수 ​​있어야합니다. using message queues with an AnimationTimer과 같은 솔루션을 사용해 보았습니다. 그것은 작동하지만 GUI가 느려집니다.

또한 메시지 대기열에서 메시지를 읽고 연결하고 TextArea (textArea.appendText(stringBuilder.toString()))에 추가하는 스레드를 실행하는 scheduled service을 사용해 보았습니다. 문제는 TextArea 컨트롤이 불안정해진다는 것입니다. 즉, 모든 텍스트를 Ctrl-A으로 강조 표시하고 창 크기를 조정하여 텍스트가 잘 보이게해야합니다. 또한 그 중 일부는 옅은 파란색 배경으로 표시되어 어떤 원인인지 확실하지 않습니다. 내 첫 번째 추측은 경쟁 조건이 컨트롤이 새로운 문자열에서 잘 업데이트되도록 허용하지 않을 수 있다는 것입니다. TextArea가 ScrollPane 주위에 싸여 있으므로 TextArea가 실제로 문제 또는 ScrollPane이있는 경우 혼동을 일으킬 수 있습니다. 이 접근 방식으로 TextArea 컨트롤이 메시지로 빠르게 업데이트되지는 않는다는 점을 언급해야합니다.

제가 업데이트를 수행하는 것에 대해 bindingTextArea.TextProperty()으로 생각했지만 메시지 (서비스 또는 고독한 스레드가 될 것임)가 여전히 다른 것을 실행하고 있다는 것을 제대로 알았는지 확실하지 않습니다. GUI 스레드

log4j와 같은 다른 알려진 로깅 프레임 워크 솔루션을 조사하려고 시도했지만 here을 언급 한 일부 물건은 TextArea에 대한 스레드를 통한 로깅에 대한 명백한 접근법을 제공하지 않는 것으로 보입니다. 또한 로깅 수준 등과 같이 사전 정의 된 메커니즘을 이미 갖추고 있으므로 로깅 시스템을 구축하는 아이디어가 마음에 들지 않습니다.

나는 this을 보았습니다. 그것은 컨트롤을 업데이트하기 위해 SwingUtilities.invokeLater(Runnable)을 사용하는 것을 의미하지만 이미 작업자 스레드에서 실행되는 javafx.application.platform.runLater()을 사용하여 유사한 접근을 시도했습니다. 내가 뭘 잘못하고 있었는지 확실하지 않지만 그냥 멈췄다. 메시지를 낼 수는 있지만 공격적 일 때는 그렇지 않습니다. 순수 동기식으로 실행되는 작업자 스레드는 실제로 초당 약 20 개 이상의 평균 라인을 생성 할 수 있으며 디버그 모드에있을 때 더 많이 사용할 수 있습니다. 가능한 해결 방법은 메시지 대기열을 추가하는 것이지만 더 이상 이해가되지 않습니다.

+0

가상화 제어, 즉 [ListView에 (HTTP를 사용하는 텍스트 영역을 사용하지 마십시오 : // docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html). – jewelsea

+0

@jewelsea 고맙습니다. ListView의 레이아웃을 TextArea처럼 보이게 설정할 수 있습니까? – konsolebox

답변

15

로그 view.css

.root { 
    -fx-padding: 10px; 
} 

.log-view .list-cell { 
    -fx-background-color: null; // removes alternating list gray cells. 
} 

.log-view .list-cell:debug { 
    -fx-text-fill: gray; 
} 

.log-view .list-cell:info { 
    -fx-text-fill: green; 
} 

.log-view .list-cell:warn { 
    -fx-text-fill: purple; 
} 

.log-view .list-cell:error { 
    -fx-text-fill: red; 
} 

LogViewer.java

import javafx.animation.Animation; 
import javafx.animation.KeyFrame; 
import javafx.animation.Timeline; 
import javafx.application.Application; 
import javafx.beans.binding.Bindings; 
import javafx.beans.property.*; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.collections.transformation.FilteredList; 
import javafx.css.PseudoClass; 
import javafx.geometry.Pos; 
import javafx.scene.Scene; 
import javafx.scene.control.*; 
import javafx.scene.layout.HBox; 
import javafx.scene.layout.Priority; 
import javafx.scene.layout.VBox; 
import javafx.stage.Stage; 
import javafx.util.Duration; 

import java.text.SimpleDateFormat; 
import java.util.Collection; 
import java.util.Date; 
import java.util.Random; 
import java.util.concurrent.BlockingDeque; 
import java.util.concurrent.LinkedBlockingDeque; 

class Log { 
    private static final int MAX_LOG_ENTRIES = 1_000_000; 

    private final BlockingDeque<LogRecord> log = new LinkedBlockingDeque<>(MAX_LOG_ENTRIES); 

    public void drainTo(Collection<? super LogRecord> collection) { 
     log.drainTo(collection); 
    } 

    public void offer(LogRecord record) { 
     log.offer(record); 
    } 
} 

class Logger { 
    private final Log log; 
    private final String context; 

    public Logger(Log log, String context) { 
     this.log = log; 
     this.context = context; 
    } 

    public void log(LogRecord record) { 
     log.offer(record); 
    } 

    public void debug(String msg) { 
     log(new LogRecord(Level.DEBUG, context, msg)); 
    } 

    public void info(String msg) { 
     log(new LogRecord(Level.INFO, context, msg)); 
    } 

    public void warn(String msg) { 
     log(new LogRecord(Level.WARN, context, msg)); 
    } 

    public void error(String msg) { 
     log(new LogRecord(Level.ERROR, context, msg)); 
    } 

    public Log getLog() { 
     return log; 
    } 
} 

enum Level { DEBUG, INFO, WARN, ERROR } 

class LogRecord { 
    private Date timestamp; 
    private Level level; 
    private String context; 
    private String message; 

    public LogRecord(Level level, String context, String message) { 
     this.timestamp = new Date(); 
     this.level  = level; 
     this.context = context; 
     this.message = message; 
    } 

    public Date getTimestamp() { 
     return timestamp; 
    } 

    public Level getLevel() { 
     return level; 
    } 

    public String getContext() { 
     return context; 
    } 

    public String getMessage() { 
     return message; 
    } 
} 

class LogView extends ListView<LogRecord> { 
    private static final int MAX_ENTRIES = 10_000; 

    private final static PseudoClass debug = PseudoClass.getPseudoClass("debug"); 
    private final static PseudoClass info = PseudoClass.getPseudoClass("info"); 
    private final static PseudoClass warn = PseudoClass.getPseudoClass("warn"); 
    private final static PseudoClass error = PseudoClass.getPseudoClass("error"); 

    private final static SimpleDateFormat timestampFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); 

    private final BooleanProperty  showTimestamp = new SimpleBooleanProperty(false); 
    private final ObjectProperty<Level> filterLevel = new SimpleObjectProperty<>(null); 
    private final BooleanProperty  tail   = new SimpleBooleanProperty(false); 
    private final BooleanProperty  paused  = new SimpleBooleanProperty(false); 
    private final DoubleProperty  refreshRate = new SimpleDoubleProperty(60); 

    private final ObservableList<LogRecord> logItems = FXCollections.observableArrayList(); 

    public BooleanProperty showTimeStampProperty() { 
     return showTimestamp; 
    } 

    public ObjectProperty<Level> filterLevelProperty() { 
     return filterLevel; 
    } 

    public BooleanProperty tailProperty() { 
     return tail; 
    } 

    public BooleanProperty pausedProperty() { 
     return paused; 
    } 

    public DoubleProperty refreshRateProperty() { 
     return refreshRate; 
    } 

    public LogView(Logger logger) { 
     getStyleClass().add("log-view"); 

     Timeline logTransfer = new Timeline(
       new KeyFrame(
         Duration.seconds(1), 
         event -> { 
          logger.getLog().drainTo(logItems); 

          if (logItems.size() > MAX_ENTRIES) { 
           logItems.remove(0, logItems.size() - MAX_ENTRIES); 
          } 

          if (tail.get()) { 
           scrollTo(logItems.size()); 
          } 
         } 
       ) 
     ); 
     logTransfer.setCycleCount(Timeline.INDEFINITE); 
     logTransfer.rateProperty().bind(refreshRateProperty()); 

     this.pausedProperty().addListener((observable, oldValue, newValue) -> { 
      if (newValue && logTransfer.getStatus() == Animation.Status.RUNNING) { 
       logTransfer.pause(); 
      } 

      if (!newValue && logTransfer.getStatus() == Animation.Status.PAUSED && getParent() != null) { 
       logTransfer.play(); 
      } 
     }); 

     this.parentProperty().addListener((observable, oldValue, newValue) -> { 
      if (newValue == null) { 
       logTransfer.pause(); 
      } else { 
       if (!paused.get()) { 
        logTransfer.play(); 
       } 
      } 
     }); 

     filterLevel.addListener((observable, oldValue, newValue) -> { 
      setItems(
        new FilteredList<LogRecord>(
          logItems, 
          logRecord -> 
           logRecord.getLevel().ordinal() >= 
           filterLevel.get().ordinal() 
        ) 
      ); 
     }); 
     filterLevel.set(Level.DEBUG); 

     setCellFactory(param -> new ListCell<LogRecord>() { 
      { 
       showTimestamp.addListener(observable -> updateItem(this.getItem(), this.isEmpty())); 
      } 

      @Override 
      protected void updateItem(LogRecord item, boolean empty) { 
       super.updateItem(item, empty); 

       pseudoClassStateChanged(debug, false); 
       pseudoClassStateChanged(info, false); 
       pseudoClassStateChanged(warn, false); 
       pseudoClassStateChanged(error, false); 

       if (item == null || empty) { 
        setText(null); 
        return; 
       } 

       String context = 
         (item.getContext() == null) 
           ? "" 
           : item.getContext() + " "; 

       if (showTimestamp.get()) { 
        String timestamp = 
          (item.getTimestamp() == null) 
            ? "" 
            : timestampFormatter.format(item.getTimestamp()) + " "; 
        setText(timestamp + context + item.getMessage()); 
       } else { 
        setText(context + item.getMessage()); 
       } 

       switch (item.getLevel()) { 
        case DEBUG: 
         pseudoClassStateChanged(debug, true); 
         break; 

        case INFO: 
         pseudoClassStateChanged(info, true); 
         break; 

        case WARN: 
         pseudoClassStateChanged(warn, true); 
         break; 

        case ERROR: 
         pseudoClassStateChanged(error, true); 
         break; 
       } 
      } 
     }); 
    } 
} 

class Lorem { 
    private static final String[] IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque hendrerit imperdiet mi quis convallis. Pellentesque fringilla imperdiet libero, quis hendrerit lacus mollis et. Maecenas porttitor id urna id mollis. Suspendisse potenti. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras lacus tellus, semper hendrerit arcu quis, auctor suscipit ipsum. Vestibulum venenatis ante et nulla commodo, ac ultricies purus fringilla. Aliquam lectus urna, commodo eu quam a, dapibus bibendum nisl. Aliquam blandit a nibh tincidunt aliquam. In tellus lorem, rhoncus eu magna id, ullamcorper dictum tellus. Curabitur luctus, justo a sodales gravida, purus sem iaculis est, eu ornare turpis urna vitae dolor. Nulla facilisi. Proin mattis dignissim diam, id pellentesque sem bibendum sed. Donec venenatis dolor neque, ut luctus odio elementum eget. Nunc sed orci ligula. Aliquam erat volutpat.".split(" "); 
    private static final int MSG_WORDS = 8; 
    private int idx = 0; 

    private Random random = new Random(42); 

    synchronized public String nextString() { 
     int end = Math.min(idx + MSG_WORDS, IPSUM.length); 

     StringBuilder result = new StringBuilder(); 
     for (int i = idx; i < end; i++) { 
      result.append(IPSUM[i]).append(" "); 
     } 

     idx += MSG_WORDS; 
     idx = idx % IPSUM.length; 

     return result.toString(); 
    } 

    synchronized public Level nextLevel() { 
     double v = random.nextDouble(); 

     if (v < 0.8) { 
      return Level.DEBUG; 
     } 

     if (v < 0.95) { 
      return Level.INFO; 
     } 

     if (v < 0.985) { 
      return Level.WARN; 
     } 

     return Level.ERROR; 
    } 

} 

public class LogViewer extends Application { 
    private final Random random = new Random(42); 

    @Override 
    public void start(Stage stage) throws Exception { 
     Lorem lorem = new Lorem(); 
     Log log = new Log(); 
     Logger logger = new Logger(log, "main"); 

     logger.info("Hello"); 
     logger.warn("Don't pick up alien hitchhickers"); 

     for (int x = 0; x < 20; x++) { 
      Thread generatorThread = new Thread(
        () -> { 
         for (;;) { 
          logger.log(
            new LogRecord(
              lorem.nextLevel(), 
              Thread.currentThread().getName(), 
              lorem.nextString() 
            ) 
          ); 

          try { 
           Thread.sleep(random.nextInt(1_000)); 
          } catch (InterruptedException e) { 
           Thread.currentThread().interrupt(); 
          } 
         } 
        }, 
        "log-gen-" + x 
      ); 
      generatorThread.setDaemon(true); 
      generatorThread.start(); 
     } 

     LogView logView = new LogView(logger); 
     logView.setPrefWidth(400); 

     ChoiceBox<Level> filterLevel = new ChoiceBox<>(
       FXCollections.observableArrayList(
         Level.values() 
       ) 
     ); 
     filterLevel.getSelectionModel().select(Level.DEBUG); 
     logView.filterLevelProperty().bind(
       filterLevel.getSelectionModel().selectedItemProperty() 
     ); 

     ToggleButton showTimestamp = new ToggleButton("Show Timestamp"); 
     logView.showTimeStampProperty().bind(showTimestamp.selectedProperty()); 

     ToggleButton tail = new ToggleButton("Tail"); 
     logView.tailProperty().bind(tail.selectedProperty()); 

     ToggleButton pause = new ToggleButton("Pause"); 
     logView.pausedProperty().bind(pause.selectedProperty()); 

     Slider rate = new Slider(0.1, 60, 60); 
     logView.refreshRateProperty().bind(rate.valueProperty()); 
     Label rateLabel = new Label(); 
     rateLabel.textProperty().bind(Bindings.format("Update: %.2f fps", rate.valueProperty())); 
     rateLabel.setStyle("-fx-font-family: monospace;"); 
     VBox rateLayout = new VBox(rate, rateLabel); 
     rateLayout.setAlignment(Pos.CENTER); 

     HBox controls = new HBox(
       10, 
       filterLevel, 
       showTimestamp, 
       tail, 
       pause, 
       rateLayout 
     ); 
     controls.setMinHeight(HBox.USE_PREF_SIZE); 

     VBox layout = new VBox(
       10, 
       controls, 
       logView 
     ); 
     VBox.setVgrow(logView, Priority.ALWAYS); 

     Scene scene = new Scene(layout); 
     scene.getStylesheets().add(
      this.getClass().getResource("log-view.css").toExternalForm() 
     ); 
     stage.setScene(scene); 
     stage.show(); 
    } 

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

고맙습니다. 나는 이것을 공부할 것이다. – konsolebox

+0

나는 이것을 좋아한다. 그것을 연결하고 이미 해결 된 문제에 대해 걱정하지 마라. 공유 해줘서 고마워! –

관련 문제