- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在 log4j2 中寻找一种简单的方法来将日志附加到文本区域。
在 log4j 中,这可以通过扩展 AppenderSkeleton 类来实现,但我在 log4j2 中找不到类似的机制。
此外,使用
重新路由系统输出System.setOut(myPrintStream);
也不起作用。
有可能让它与 log4j2 一起使用吗?
最佳答案
答案很晚,但我终于找到了问题的解决方案。要将 log4j2 控制台流附加到 JavaFX TextArea、ListView 或类似的控件甚至 swing 控件,都是可能的。我的解决方案还将标准 System.out 附加到记录器 View 。看看你自己。
首先,我通过屏幕截图向您展示我当前的结果(抱歉,我无法直接将其包含在此处,因为我在 stackoverflow 上的用户帐户没有足够的声誉...): View the screenshot
第 1 步:编辑 log4j2.xml 文件并添加属性 follow="true":
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="%d %-5p (%F:%L) - %m%n" />
</Console>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
第 2 步:编写一个类,用于将 SYSTEM_OUT 附加到所需的可视控件
对于我的示例,我制作了一个小型 ui 控件组合,如屏幕截图所示。因此,您需要一个 fxml、一个 fxml Controller 以及一个 LogStringCell,它提供颜色格式(这不是一个很好的噱头)
LoggerConsole.fxml
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<VBox prefHeight="200.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tuc.plentyfx.configurator.views.LoggerConsoleController">
<children>
<ListView fx:id="listViewLog" />
<ToolBar prefHeight="40.0" prefWidth="200.0">
<items>
<Button mnemonicParsing="false" onAction="#handleRemoveSelected" text="Selektierte löschen" />
<Button fx:id="buttonClearLog" mnemonicParsing="false" onAction="#handleClearLog" text="Leeren" />
<ToggleButton fx:id="toggleButtonAutoScroll" mnemonicParsing="false" text="Auto-Scroll" />
<ChoiceBox fx:id="choiceBoxLogLevel" prefWidth="150.0" />
</items>
</ToolBar>
</children>
</VBox>
Controller 类LoggerConsoleController.java
package tuc.plentyfx.configurator.views;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.ToggleButton;
import javafx.util.Callback;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import tuc.plentyfx.common.LogStringCell;
public class LoggerConsoleController {
static final Logger logger = LogManager.getLogger(LoggerConsoleController.class.getName());
@FXML
private ListView<String> listViewLog;
@FXML
private ToggleButton toggleButtonAutoScroll;
@FXML
private ChoiceBox<Level> choiceBoxLogLevel;
@FXML
void handleRemoveSelected() {
listViewLog.getItems().removeAll(listViewLog.getSelectionModel().getSelectedItems());
}
@FXML
void handleClearLog() {
listViewLog.getItems().clear();
}
@FXML
void initialize() {
listViewLog.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
Configuration loggerConfiguration = loggerContext.getConfiguration();
LoggerConfig loggerConfig = loggerConfiguration.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
/* ChoiceBox füllen */
for (Level level : Level.values()) {
choiceBoxLogLevel.getItems().add(level);
}
/* Aktuellen LogLevel in der ChoiceBox als Auswahl setzen */
choiceBoxLogLevel.getSelectionModel().select(loggerConfig.getLevel());
choiceBoxLogLevel.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Level>() {
@Override
public void changed(ObservableValue<? extends Level> arg0, Level oldLevel, Level newLevel) {
loggerConfig.setLevel(newLevel);
loggerContext.updateLoggers(); // übernehme aktuellen LogLevel
}
});
listViewLog.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
@Override
public ListCell<String> call(ListView<String> listView) {
return new LogStringCell();
}
});
/* den Origial System.out Stream in die ListView umleiten */
PipedOutputStream pOut = new PipedOutputStream();
System.setOut(new PrintStream(pOut));
PipedInputStream pIn = null;
try {
pIn = new PipedInputStream(pOut);
}
catch (IOException e) {
e.printStackTrace();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(pIn));
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
while (!isCancelled()) {
try {
String line = reader.readLine();
if (line != null) {
Platform.runLater(new Runnable() {
@Override
public void run() {
listViewLog.getItems().add(line);
/* Auto-Scroll + Select */
if (toggleButtonAutoScroll.selectedProperty().get()) {
listViewLog.scrollTo(listViewLog.getItems().size() - 1);
listViewLog.getSelectionModel().select(listViewLog.getItems().size() - 1);
}
}
});
}
}
catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
};
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
}
LogStringCell.class
package tuc.plentyfx.common;
import javafx.scene.control.ListCell;
import javafx.scene.layout.FlowPane;
import javafx.scene.text.Text;
public class LogStringCell extends ListCell<String> {
@Override
protected void updateItem(String string, boolean empty) {
super.updateItem(string, empty);
if (string != null && !isEmpty()) {
setGraphic(createAssembledFlowPane(string));
}
else {
setGraphic(null);
setText(null);
}
}
/* Erzeuge ein FlowPane mit gefüllten Textbausteien */
private FlowPane createAssembledFlowPane(String... messageTokens) {
FlowPane flow = new FlowPane();
for (String token : messageTokens) {
Text text = new Text(token);
if (text.toString().contains(" TRACE ")) {
text.setStyle("-fx-fill: #0000FF");
}
if (text.toString().contains(" ALL ")) {
text.setStyle("-fx-fill: #FF00FF");
}
if (text.toString().contains(" ERROR ")) {
text.setStyle("-fx-fill: #FF8080");
}
if (text.toString().contains(" INFO ")) {
text.setStyle("-fx-fill: #000000");
}
if (text.toString().contains(" FATAL ")) {
text.setStyle("-fx-fill: #FF0000");
}
if (text.toString().contains(" DEBUG ")) {
text.setStyle("-fx-fill: #808080");
}
if (text.toString().contains(" OFF ")) {
text.setStyle("-fx-fill: #8040FF");
}
if (text.toString().contains(" WARN ")) {
text.setStyle("-fx-fill: #FF8000");
}
flow.getChildren().add(text);
}
return flow;
}
}
第 3 步:在您的应用程序中使用它。完成...!
问题/想法/局限性:目前,我的代码在与线程一起使用时存在一些问题:从另一个线程创建日志语句将破坏管道流并引发错误。也许需要一个同步管道。我通过谷歌找到了相关代码,但还没有尝试过(也可以在这里查看: http://www.certpal.com/blogs/2010/11/using-a-pipedinputstream-and-pipedoutputstream/):
package application.common;
import java.io.InputStream;
import java.io.OutputStream;
public class SyncPipe implements Runnable {
private final OutputStream outputStream;
private final InputStream inputStream;
public SyncPipe(InputStream inputStream, OutputStream outputStream) {
this.inputStream = inputStream;
this.outputStream = outputStream;
}
@Override
public void run() {
try {
final byte[] buffer = new byte[1024];
for (int length = 0; (length = inputStream.read(buffer)) != -1;) {
outputStream.write(buffer, 0, length);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
另一个问题:彩色字符串格式应该通过对日志消息类型(信息、调试、跟踪等)进行更好的检测来重新编码。使用“text.toString().contains("TRACE "))”之类的内容进行过滤真的很难看。
如果您对此有任何疑问,请告诉我。该帖子确实很旧,但如果您在这里写信,我会收到一封电子邮件。那我就可以直接回答你了
关于java - 将 log4j2 输出附加到 TextArea,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21475576/
我关注了 tutorial on creating a popup for an add-on在 Firefox 中,效果很好。 我现在遇到的问题是它创建的弹出窗口不会更改大小以适应我添加到其中的内容
我有一些视频,我需要连接一个标题并添加一些覆盖,问题是我需要先做覆盖,否则时间不正确,然后才将标题连接到视频的开头 ffmpeg -i talk.mp4 -i start_pancarte.png
我正在尝试附加一个 CSV 文件。这是我正在使用的线路。不幸的是,我找不到 export-csv 的附加选项。任何想法都有助于使其发挥作用。 Get-ADGroupMember "Domain Adm
我正在努力理解 Attach API (com.sun.tools.attach.*) 的用途。它的典型用途是什么?它是为了“模拟”JVM,以便您可以在不部署/启动代码的情况下测试您的代码吗?它是一个
我不明白为什么这不起作用。 soup_main = BeautifulSoup('FooBar') soup_append = BeautifulSoup('Meh') soup_main.body.
我有以下代码来返回我想要的字符串 $sql = " SELECT `description` FROM `auctions` WHERE `description` REGEX
我正在尝试从数组中附加具有多个值的元素,但我做错了。这是我的代码: for(var i=0; i ` + pricesArray[i].start_date ` ` + pricesArray[i
我正在尝试将图像链接添加到此 javascript 附加表中。使图像位于按钮上方 这是代码 $("#1").append(""+section+""+no+""+price+""+button+""
我有一个问题,我已经解决了,但它太烦人了。 我有一个 js 代码,当使用“追加”按下按钮时,它会放下一些 html 代码,并且通过该代码,我为 x 按钮提供了一个 id,并为容器元素提供了一个 id。
我想逐行读取文件,并且每一行可能都有很多字符。 这个版本的readline效果很好 func readLine(r *bufio.Reader) ([]byte, error) { var (
我有一个网站,每次用户登录或注销时,我都会将其保存到文本文件中。 如果不存在,我的代码在附加数据或创建文本文件时不起作用。这是示例代码 $myfile = fopen("logs.txt", "wr"
我正在尝试使用 typescript 和 Formik 创建一个自定义输入字段。我可以就完成以下代码的最佳方式获得一些帮助吗?我需要添加额外的 Prop 标签和名称......我已经坚持了一段时间,希
我有一个字符串 big_html,我想将它添加到某个 div 中。我观察到以下方面的性能差异: $('#some-div').append( big_html ); // takes about 10
如何使用 FormData 创建以下结果 ------WebKitFormBoundaryOmz20xyMCkE27rN7 Content-Disposition: form-data; name="
有没有办法附加 jQuery 事件处理程序,以便在任何先前附加的事件处理程序之前触发该处理程序?我遇到了this article ,但代码不起作用,因为事件处理程序不再存储在数组中,而这正是他的代码所
我正在开发一个需要网络登录的 iPhone 应用程序。像往常一样我打电话 [[UIApplication sharedApplication] openURL:loginURL]; 这将关闭应用程序并
我想开发一个仅针对特定域激活的扩展。 我不希望它在不浏览此特定域时出现在浏览器菜单中。 有可能这样做吗? 最佳答案 可能:对于菜单,您可以添加一个弹出窗口侦听器,用于检查当前加载的URL(docs f
这段 JavaScript 代码 function writeCookie(CookieName, CookieValue, CookieDuration) { var expiration
我正在使用 Handlebars 来渲染使用ajax从本地服务器获得的信息。我的 HTML 看起来像: {{#each Tabs}}
我尝试了以下代码,但当输入框中没有数据时它不会通知。当我直接添加此内容(不附加)时,它会起作用。我在这里做错了什么 var output = "\n"+ "\n"+
我是一名优秀的程序员,十分优秀!