说明
用JavaFX写了个工具, 想将控制台的日志输出到一个文本域中, 同时控制显示的最大行数
实现
重定向
查看了logback
的输出方式, 实际应该就是用的System.out
, 具体查看ch.qos.logback.core.ConsoleAppender
, 因此重定向到TextArea
只需要覆盖System.out
的原PrintStream
, 然后将文本内容追加到文本域中
自定义一个输出流, 实现向文本域追加文本
重写write方法, 将文本内容转为字符串添加到文本域
@AllArgsConstructor(access = AccessLevel.PROTECTED)
private static class TextAreaOutputStream extends OutputStream {
private final TextArea textArea;
@Override
public void write(int b) {
Platform.runLater(() -> {
textArea.appendText(String.valueOf((char) b));
textArea.setScrollTop(Double.MAX_VALUE);
});
}
@Override
public void write(@Nonnull byte[] b, int off, int len) {
Platform.runLater(() -> {
textArea.appendText(new String(b, off, len));
textArea.setScrollTop(Double.MAX_VALUE);
});
}
}
覆盖System.out流
public static TextArea wrap(TextArea textArea, Long maxLine) {
// 关掉旧输出流
IOUtils.closeQuietly(System.out);
// 新的输出流
final PrintStream nextPrintStream = new PrintStream(new TextAreaOutputStream(textArea), true);
// 重定向流
System.setOut(nextPrintStream);
System.setErr(System.out);
return textArea;
}
保留固定行数
控制文本域显示的最大行数, 即超过一定行数删除旧的行数
自定义文本内容监听器
实现一个工厂方法, 接收TextArea
和Long
(最大行数), 每次值变化后检查当前行数, 如果超过指定行数, 则只保留指定行数, 重新拼接后覆盖文本域原有内容
暂时想不到其他更有效率的方法, 应该可以考虑设计一个缓冲区, 指定一个有界队列, 先进先出, 超过指定深度后多于行数从头部poll出去, 但是在文本域中显示之前还是需要重新拼接, 感觉都一样
private static final BiFunction<TextArea, Long, ChangeListener<? super String>> lineClampListener = (t, l) -> (observable, oldValue, newValue) -> Platform.runLater(() -> {
final String[] lines = newValue.split(StrUtil.LF);
if (lines.length > l) {
t.setText(
IntStream.range(lines.length - l.intValue(), lines.length).collect(
StringBuilder::new,
(s, i) -> s.append(lines[i]).append(StrUtil.LF),
(s1, s2) -> {
// ignore
}
).toString()
);
t.setScrollTop(Double.MAX_VALUE);
}
});
添加监听器到TextArea
textArea.textProperty().addListener(lineClampListener.apply(textArea, maxLine));
完整代码
public class ConsoleOutputRedirector {
private static final BiFunction<TextArea, Long, ChangeListener<? super String>> lineClampListener = (t, l) -> (observable, oldValue, newValue) -> Platform.runLater(() -> {
final String[] lines = newValue.split(StrUtil.LF);
if (lines.length > l) {
t.setText(
IntStream.range(lines.length - l.intValue(), lines.length).collect(
StringBuilder::new,
(s, i) -> s.append(lines[i]).append(StrUtil.LF),
(s1, s2) -> {
// ignore
}
).toString()
);
t.setScrollTop(Double.MAX_VALUE);
}
});
public static TextArea wrap(TextArea textArea) {
// 默认不限制行数
return wrap(textArea, Long.MAX_VALUE);
}
public static TextArea wrap(TextArea textArea, Long maxLine) {
// 关掉旧输出流
IOUtils.closeQuietly(System.out);
// 控制行数的监听器
textArea.textProperty().addListener(lineClampListener.apply(textArea, maxLine));
// 新的输出流
final PrintStream nextPrintStream = new PrintStream(new TextAreaOutputStream(textArea), true);
// 重定向流
System.setOut(nextPrintStream);
System.setErr(System.out);
return textArea;
}
@AllArgsConstructor(access = AccessLevel.PROTECTED)
private static class TextAreaOutputStream extends OutputStream {
private final TextArea textArea;
@Override
public void write(int b) {
Platform.runLater(() -> {
textArea.appendText(String.valueOf((char) b));
textArea.setScrollTop(Double.MAX_VALUE);
});
}
@Override
public void write(@Nonnull byte[] b, int off, int len) {
Platform.runLater(() -> {
textArea.appendText(new String(b, off, len));
textArea.setScrollTop(Double.MAX_VALUE);
});
}
}
}
使用
使用时指定一个输出的目标文本域即可
TextArea textArea = ConsoleOutputRedirector.wrap(new TextArea(), 100L);
效果
效果就是控制台所有内容都会输出到这个文本域, 同时最大只会显示指定行数
但是有一个问题, 如果控制台输出过快, 因为文本替换效率的问题, 会有明显卡顿, 过快是指并发输出大量日志, 上千上万条, 一般每个指定行数区间间隔个个别毫秒是没问题的