java - 如何在JavaFX TextArea中显示日志?

标签 java logging javafx

我有一个 Java 记录器,当前使用下面指定的设置输出到文件。我还有一个 JavaFX TextArea,我希望我的记录器同时写入文件和 TextArea。

记录器设置:

java.util.logging.FileHandler.level     = ALL
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.FileHandler.append    = true
java.util.logging.FileHandler.pattern   = log.txt

记录器声明:

static Logger LOGGER;
static {
    try(FileInputStream ins = new FileInputStream("src/main/resources/log.config")) {
        LogManager.getLogManager().readConfiguration(ins);
        LOGGER = Logger.getLogger(BuildGet.class.getName());
        initialContext = new InitialContext();
    } catch (NamingException | IOException e) {
        LOGGER.log(Level.WARNING,"ERROR LOGGER", e);
    }
}

JavaFX 文本区域:

```xml``<TextArea layoutX="20.0" layoutY="755.0" prefHeight="80.0" prefWidth="560.0" fx:id="logTextArea"/>`

播放示例时出现错误:

javafx.fxml.LoadException: 
/C:/Users/slizo/Desktop/DConsole/target/classes/DConsoleScene.fxml:18

at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:944)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:981)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:230)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:755)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2808)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2634)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2516)
at jmxClientConsole.gui.DConsoleFrame.start(DConsoleFrame.java:46)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.NoSuchMethodException: jmxClientConsole.DConsoleFrameControllerGetMessage.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3349)
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2553)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:939)
... 17 more

这是我的启动类(class):

package jmxClientConsole.gui;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import jmxClientConsole.BuildGet;
import jmxClientConsole.TextAreaHandler;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;


public class ConsoleFrame extends Application {

static Logger LOGGER;
static {
    try(FileInputStream ins = new FileInputStream("src/main/resources/log.config")) {
        LogManager.getLogManager().readConfiguration(ins);
        LOGGER = Logger.getLogger(BuildGet.class.getName());
        LOGGER.setUseParentHandlers(false);
    } catch (IOException e) {
        LOGGER.log(Level.WARNING," Eror ConsoleFrame ", e);
    }
}
public static void main(String[] args) {
    launch(args);
}

@Override
public void start(Stage stage)  {
    try {
        FXMLLoader loader = new FXMLLoader();
        URL xmlUrl = getClass().getResource("/ConsoleScene.fxml");
        loader.setLocation(xmlUrl);
        TextArea textArea = new TextArea();
        textArea.setEditable(false);
        textArea.setFont(Font.font("Monospaced", 13));

        TextAreaHandler handler = new TextAreaHandler(textArea);
        handler.setFormatter(new SimpleFormatter());
        LOGGER.addHandler(handler);

        Parent root = loader.load();
        stage.setTitle("Console");
        stage.getIcons().add(new Image(ConsoleFrame.class.getClassLoader().getResourceAsStream("logoC.png")));
        stage.setScene(new Scene(root));
        stage.show();
        LOGGER.log(Level.INFO,"Success console ");
    } catch (Exception e) {
        LOGGER.log(Level.SEVERE,"Eror Console", e);
    }
}
}

我在关于 Controller 的评论中写的,我有这个。我里面有按钮的所有逻辑:

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="900.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jmxClientConsole.ConsoleFrameControllerGetMessage">

最佳答案

最终,您需要一个 java.util.logging.Handler将日志消息附加到 TextArea 中。这是一个相对简单的实现:

package sample;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.logging.ErrorManager;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import javafx.application.Platform;
import javafx.scene.control.TextArea;

public class TextAreaHandler extends Handler {

    // Avoid doing too much work at once. Tune this value as needed.
    private static final int MAX_APPEND = 100;

    private final Queue<LogRecord> recordQueue = new ArrayDeque<>();
    private boolean notify = true;

    private final TextArea textArea;
    private volatile boolean open = true;

    public TextAreaHandler(TextArea textArea) {
        this.textArea = textArea;
    }

    @Override
    public void publish(LogRecord record) {
        if (open && isLoggable(record)) {
            if (Platform.isFxApplicationThread()) {
                appendRecord(record);
            } else {
                synchronized (recordQueue) {
                    recordQueue.add(record);
                    if (notify) {
                        notify = false;
                        notifyFxThread();
                    }
                }
            }
        }
    }

    private void notifyFxThread() {
        try {
            Platform.runLater(this::processQueue);
        } catch (Exception ex) {
            reportError(null, ex, ErrorManager.GENERIC_FAILURE);
        }
    }

    private void processQueue() {
        List<LogRecord> records = new ArrayList<>();
        synchronized (recordQueue) {
            while (!recordQueue.isEmpty() && records.size() < MAX_APPEND) {
                records.add(recordQueue.remove());
            }

            if (recordQueue.isEmpty()) {
                notify = true;
            } else {
                notifyFxThread();
            }
        }
        records.forEach(this::appendRecord);
    }

    private void appendRecord(LogRecord record) {
        String message;
        try {
            message = getFormatter().format(record);
        } catch (Exception ex) {
            reportError(null, ex, ErrorManager.FORMAT_FAILURE);
            return;
        }

        try {
            textArea.appendText(message);
        } catch (Exception ex) {
            reportError(null, ex, ErrorManager.GENERIC_FAILURE);
        }
    }

    @Override
    public void flush() {
        /*
         * This implementation has no "buffer". If the 'recordQueue' is not
         * empty then the JavaFX Application Thread has already been notified
         * of work, meaning the queue will be emptied eventually. And if the
         * JavaFX framework has been shutdown, then there's no work to do
         * anyway.
         */
    }

    @Override
    public void close() {
        open = false;
    }
}

上面可以像这样使用:

package sample;

import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class Main extends Application {

    private static final Logger LOGGER = Logger.getLogger(Main.class.getName());

    static {
        LOGGER.setUseParentHandlers(false);
    }

    @Override
    public void start(Stage primaryStage) {
        TextArea textArea = new TextArea();
        textArea.setEditable(false);
        textArea.setFont(Font.font("Monospaced", 13));

        TextAreaHandler handler = new TextAreaHandler(textArea);
        handler.setFormatter(new SimpleFormatter());
        LOGGER.addHandler(handler);

        primaryStage.setScene(new Scene(textArea, 600, 400));
        primaryStage.show();

        LOGGER.info("This log message will be appended to the text area.");
    }
}

不幸的是,我不知道如何(如果可能的话)从文件中配置它。至少,不是没有某种方法来间接获取 TextArea(例如,依赖项注入(inject)、JNDI(?) 等)。


您已经编辑了您的问题,表示您收到此错误:

Caused by: java.lang.NoSuchMethodException: jmxClientConsole.DConsoleFrameControllerGetMessage.<init>()
     at java.base/java.lang.Class.getConstructor0(Class.java:3349)
     at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2553)
     at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:939)

这是一个与您最初询问的问题不同的问题。但是,该错误告诉您某些代码期望您的类(如错误中所指定)具有无参数构造函数,但该构造函数不存在。

如果该类用作 FXML Controller ,请注意默认 Controller 工厂需要无参构造函数。添加该构造函数或设置自定义 Controller 工厂(如何做到这一点超出了本问答的范围;应该有教程,可能还有其他 Stack Overflow 问答)。

关于java - 如何在JavaFX TextArea中显示日志?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76488986/

相关文章:

java - 将 ActorRef 获取到先前生成的 EventSourcedBehavior

java - Android 无法记住我的文件

Java 运行时依赖

logging - 在多台机器上搜索日志

javafx - 如何在框架“JAVAFX”中更改标题栏的颜色?

java - 如何从可编辑组合框中更改的文本获取事件?

java - 对 unix 文件进行阻塞写入操作

python - 在 Python 中记录特定于线程和共享的日志文件

logging - 如何使应用程序洞察仅存储异常和自定义事件

应用程序关闭时尝试获取 mainApp 中文本字段的文本时出现 java.lang.NullPointerException