【问题标题】:Java: synchronizing standard out and standard errorJava:同步标准输出和标准错误
【发布时间】:2011-09-01 13:58:36
【问题描述】:

我有一个奇怪的问题,如果我能解决它会很好。出于调试目的(以及其他一些事情),我正在标准输出上编写控制台 Java 应用程序的日志。有些东西写在标准输出上,有些东西像错误一样打印在标准错误上。问题是这两者并不完全同步,所以打印行的顺序并不总是正确的。我想这是因为打印了很多东西,并且碰巧一个输出的缓冲区已满,因此另一个输出在第一个输出刷新缓冲区之前打印。

例如,我想这样写:

syso: aaa
syso: bbb
syso: ccc
syso: ddd
syso: eee
syserr: ---

有时打印的是

aaa
bbb
ccc
---
ddd
eee

有时中间没有换行,所以看起来像

aaa
bbb
ccc---

ddd
eee

每次我在输出上打印一些东西时,我都会用

刷新相同的输出
System.out.flush();

System.err.flush();

如何解决这个问题? 顺便说一句,所有内容都打印在 Eclipse 控制台中。

【问题讨论】:

  • 您是否考虑过使用像 Log4J 这样的专用日志框架?
  • @Péter 当然,这将是一种选择,但我希望有一个简单的解决方案来解决这个问题(供将来参考,如果没有别的)。

标签: java eclipse


【解决方案1】:

问题在于终端仿真器(在您的情况下为 Eclipse)负责处理应用程序的标准输出和标准错误。如果不与终端仿真器通信,您永远无法确定outerr 以正确的顺序显示。因此,我会考虑在err 上打印所有内容并将其重定向到文件。您仍然可以使用out 进行干净的用户交互。

尽管如此,您的问题有一个(非常糟糕但严格的)解决方案:

System.out.println(...);
System.out.flush();
Thread.sleep(100);

System.err.println(...);
System.err.flush();
Thread.sleep(100);

您可能需要根据您的配置更改睡眠持续时间!

【讨论】:

  • 为什么这个解决方案很糟糕?
  • @skia.heliou 因为延迟是完全任意的,可能会根据处理器的速度等而有所不同。
【解决方案2】:

这是一个长期存在的 Eclipse 错误:https://bugs.eclipse.org/bugs/show_bug.cgi?id=32205

【讨论】:

  • 该错误已在 Eclipse 2019-09 中修复。现在你有了option to synchronize System.out and System.err(默认禁用)。但是,System.err 输出会失去红色,也会变成黑色。
  • 失去红色会消除大部分有用性。
【解决方案3】:

我知道这篇文章很古老,但它今天仍然是一个问题,所以这里使用@EserAygün 的答案解决了这个问题的另一个解决方案,但不需要您在项目中查找和修改您所在的每个地方正在写信给System.outSystem.err

为自己创建一个名为 EclipseTools 的类,其内容如下(以及所需的包声明和导入):

public class EclipseTools {

    private static OutputStream lastStream = null;
    private static boolean      isFixed    = false;

    private static class FixedStream extends OutputStream {

        private final OutputStream target;

        public FixedStream(OutputStream originalStream) {
            target = originalStream;
        }

        @Override
        public void write(int b) throws IOException {
            if (lastStream!=this) swap();
            target.write(b);
        }

        @Override
        public void write(byte[] b) throws IOException {
            if (lastStream!=this) swap();
            target.write(b);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            if (lastStream!=this) swap();
            target.write(b, off, len);
        }

        private void swap() throws IOException {
            if (lastStream!=null) {
                lastStream.flush();
                try { Thread.sleep(200); } catch (InterruptedException e) {}
            }
            lastStream = this;
        }

        @Override public void close() throws IOException { target.close(); }
        @Override public void flush() throws IOException { target.flush(); }
    }

    /**
     * Inserts a 200ms delay into the System.err or System.out OutputStreams
     * every time the output switches from one to the other. This prevents
     * the Eclipse console from showing the output of the two streams out of
     * order. This function only needs to be called once.
     */
    public static void fixConsole() {
        if (isFixed) return;
        isFixed = true;
        System.setErr(new PrintStream(new FixedStream(System.err)));
        System.setOut(new PrintStream(new FixedStream(System.out)));
    }
}

然后,只需在代码开头调用一次EclipseTools.fixConsole()。问题解决了。

基本上,这会将System.errSystem.out 两个流替换为一组自定义流,这些流仅将其数据转发到原始流,但会跟踪最后写入哪个流。如果写入的流发生更改,例如 System.err.something(...) 后跟 System.out.something(...),它会刷新最后一个流的输出并等待 200 毫秒,让 Eclipse 控制台有时间完成打印。

注意:200ms 只是一个粗略的初始值。如果此代码减少了,但没有为您消除问题,请将Thread.sleep 的延迟从 200 增加到更高的值,直到它起作用。或者,如果这种延迟有效但影响了代码的性能(如果您经常交替流),您可以尝试逐渐减少它,直到开始出现错误。

【讨论】:

    【解决方案4】:

    java.lang.System.setErr(java.lang.System.out);

    使应用程序使用标准输出作为错误流。

    【讨论】:

    • 这样我就无法区分std out和std err(它们都像往常一样用黑色打印,而不是用黑红色打印)。
    【解决方案5】:
    public class Util
    
        synchronized public static void printToOut(...)
            out.print(...)
    
        synchronized public static void printToErr(...)
            err.print(...)
    

    【讨论】:

    • 这是可能的;即使我们在 JVM 中同步这两个流,它们仍然可以在终端设备上异步输出。
    【解决方案6】:

    问题在于Eclipse Console的使用。通常,std out 将一次写入一个字节到控制台,std err 也会,但是是红色的。但是,该方法在返回之前不会等待所有字节都被写入。所以,我推荐的是:

    import java.io.OutputStream;
    import java.io.PrintStream;
    import java.util.function.IntConsumer;
    
    public final class Printer extends PrintStream {
        public static final Printer out = new Printer(
                e -> System.out.print((char) e));
        public static final Printer err = new Printer(
                e -> System.err.print((char) e));
        private final IntConsumer printer;
        private static final Object lock = "";
    
        private Printer(IntConsumer printer) {
            super(new OutputStream() {
                public void write(int b) {
                    printer.accept(b);
                }
            });
            this.printer = printer;
        }
    
        public void print(int x) {
            synchronized (lock) {
                this.print(Integer.toString(x));
            }
        }
    
        public void print(boolean x) {
            synchronized (lock) {
                this.print(Boolean.toString(x));
            }
        }
    
        public void print(double x) {
            synchronized (lock) {
                this.print(Double.toString(x));
            }
        }
    
        public void print(float x) {
            synchronized (lock) {
                this.print(Float.toString(x));
            }
        }
    
        public void print(long x) {
            synchronized (lock) {
                this.print(Long.toString(x));
            }
        }
    
        public void print(char x) {
            synchronized (lock) {
                this.print(Character.toString(x));
            }
        }
    
        public void print(char[] x) {
            synchronized (lock) {
                StringBuffer str = new StringBuffer(x.length);
                for (char c : x) {
                    str.append(c);
                }
                this.print(str);
            }
        }
    
        public void print(Object x) {
            synchronized (lock) {
                this.print(x.toString());
            }
        }
    
        public void print(String x) {
            synchronized (lock) {
                x.chars().forEach(printer);
            }
        }
    
        public void println(int x) {
            synchronized (lock) {
                this.print(Integer.toString(x) + "\n");
            }
        }
    
        public void println(boolean x) {
            synchronized (lock) {
                this.print(Boolean.toString(x) + "\n");
            }
        }
    
        public void println(double x) {
            synchronized (lock) {
                this.print(Double.toString(x) + "\n");
            }
        }
    
        public void println(float x) {
            synchronized (lock) {
                this.print(Float.toString(x) + "\n");
            }
        }
    
        public void println(long x) {
            this.print(Long.toString(x) + "\n");
        }
    
        public void println(char x) {
            synchronized (lock) {
                this.print(Character.toString(x) + "\n");
            }
        }
    
        public void println(char[] x) {
            synchronized (lock) {
                StringBuffer str = new StringBuffer(x.length);
                for (char c : x) {
                    str.append(c);
                }
                this.print(str + "\n");
            }
        }
    
        public void println(Object x) {
            synchronized (lock) {
                this.print(x.toString() + "\n");
            }
        }
    
        public void println(String x) {
            synchronized (lock) {
                x.chars().forEach(printer);
                printer.accept('\n');
            }
        }
    }
    

    使用Printer.outPrinter.err 代替System.outSystem.err。它仍然有相同的错误,但效果要好得多。

    【讨论】:

      猜你喜欢
      • 2011-02-23
      • 2011-06-26
      • 1970-01-01
      • 2014-07-22
      • 2022-07-23
      • 1970-01-01
      • 2014-08-25
      • 1970-01-01
      相关资源
      最近更新 更多