我有一个简单的自定义日志记录框架,如下所示:
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;
}
}
现在,我要做的就是扩展它,以便它能够正确处理通过线程记录的消息。我已经尝试过将解决方案与AnimationTimer一起使用消息队列。它可以工作,但是会降低GUI的速度。
我还尝试使用计划的服务,该服务运行一个线程,该线程从消息队列中读取消息,将它们连接起来,并将其附加到TextArea(textArea.appendText(stringBuilder.toString())
)。问题在于TextArea控件变得不稳定,即您必须使用Ctrl-A
并尝试调整窗口大小以使其看起来不错。也有一些以浅蓝色背景显示,不确定是什么原因造成的。我的第一个猜测是竞争条件可能不允许控件从新字符串很好地进行自我更新。还值得注意的是,textarea包裹在ScrollPane周围,因此如果TextArea实际上是出现问题或使用ScrollPane的文本区域,则会增加混乱。我还必须提到,这种方法不会使TextArea控件自身通过消息快速更新。
我考虑过binding
TextArea.TextProperty()
要进行更新的内容,但不确定消息收集器(通过服务还是单独的线程)仍将与GUI线程不同地运行,因此我不确定如何正确地执行此操作。
我尝试查找其他已知的日志记录框架解决方案,例如log4j以及此处引用的一些内容,但似乎都没有提供通过线程到TextArea进行日志记录的明显方法。我也不喜欢在它们之上构建我的日志记录系统的想法,因为它们已经具有诸如日志记录级别等预定义的机制。
我也看到了这一点。这意味着使用SwingUtilities.invokeLater(Runnable)
了更新控件,但是我已经尝试了一种类似的方法,javafx.application.platform.runLater()
该方法在工作线程上执行。我不确定我是否做错了什么,但是它挂了。它可以产生消息,但是当它们足够激进时则不能。我估计以纯同步方式运行的工作线程实际上可以每秒产生约20条或更多的平均行,甚至在调试模式下也可以产生更多。可能的解决方法是也向其中添加消息队列,但这不再有意义。
log-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);
}
}
下面有关可选文本的部分是对上面发布的解决方案的补充。如果不需要可选文本,则可以忽略下面的选择。
是否可以选择文本?
我有一个简单的自定义日志框架,如下所示: 现在我要做的是扩展它,使它能够正确地处理通过线程记录消息。我尝试过一些解决方案,比如使用带有AnimationTimer的消息队列。它可以工作,但会减慢GUI的速度。 我已经试着查阅了其他已知的日志记录框架解决方案,比如log4j和这里提到的一些工具,但它们似乎都没有给出通过线程到textarea进行日志记录的明显方法。我也不喜欢在它们上面构建日志系统的想
本文向大家介绍C#记录消息到日志文件的方法,包括了C#记录消息到日志文件的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C#记录消息到日志文件的方法。分享给大家供大家参考。具体实现方法如下: 希望本文所述对大家的C#程序设计有所帮助。
将PostSharp用于C#应用程序,我有以下场景: Namespace_ACustomLoggingMethod Namespace_B.DoThings thingMethod(实际上是几种不同的方法) DoMomthingMethod调用CustomLoggingMethod,它以所需的格式创建日志条目并且运行良好。正如预期的那样,日志条目将源记录为CustomLoggingMethod,我
问题内容: 我对jdk日志记录配置有疑问。我有一个使用JDK Logging输出消息的EJB(已部署到glassfish中)。因此,我使用具有以下代码的命名记录器: 我知道可以通过将以下行添加到Glassfish的logging.properties文件中来为记录器配置日志级别: 但是,如何为记录器指定输出文件?我想将来自名为“ org.imixs.workflow”的记录器的所有消息放入单独的文
这是我第一次使用 Spring Boot 开发 REST API。我想在收到错误请求 400 错误时返回自定义消息。 我有我的控制器: 我想用最简单的方法返回400错误: 带有自定义消息。 我试图创建一个@控制器,但它不起作用,因为我的控制器返回了一个列表 有没有一种方法可以轻松创建自定义消息?
我正忙于GEB/Spock中的e2e测试,我想知道如何添加自定义消息。现在我只得到这样一个stacktrace: 等...