2011-05-25 32 views
9

Tengo un problema extraño y sería bueno si pudiera resolverlo. Para fines de depuración (y algunas otras cosas, también) estoy escribiendo un registro de una aplicación Java de consola en el resultado estándar. Algunas cosas que se escriben en estándar, y algunas cosas como errores se imprimen en error estándar. El problema es que estos dos no están perfectamente sincronizados, por lo que el orden de las líneas impresas no siempre es correcto. Supongo que esto se debe a que se imprimen muchas cosas y sucede que un buffer para una salida está lleno, por lo que la otra salida se imprime antes de que la primera la vacíe.Java: sincronización de salida estándar y error estándar

Por ejemplo, yo quiero escribir esto:

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

Lo que a veces se imprime es

aaa 
bbb 
ccc 
--- 
ddd 
eee 

A veces no hay una nueva línea en el medio, por lo que parece

aaa 
bbb 
ccc--- 

ddd 
eee 

Cada vez que imprimo algo en una salida, limpio la misma salida con

System.out.flush(); 

o

System.err.flush(); 

¿Cómo resolver este problema? Por cierto, todo está impreso en la consola Eclipse.

+1

¿Ha considerado utilizar un marco de registro dedicado como Log4J en su lugar? –

+1

@ Péter seguro, esa sería una opción, pero preferiría que hubiera una solución simple a este problema (para referencia futura, si nada más). – Ivan

Respuesta

0
public class Util 

    synchronized public static void printToOut(...) 
     out.print(...) 

    synchronized public static void printToErr(...) 
     err.print(...) 
+2

Esto no funciona. (al menos en Ubuntu 12.04 con Java 1.6) – Ankit

+3

eso es posible; incluso si sincronizamos las dos transmisiones en JVM, aún pueden aparecer de manera asincrónica en dispositivos terminales. – irreputable

1

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

hace que la aplicación utilice la salida estándar como corriente de error.

+2

De esta manera no puedo hacer una diferencia entre std out y std err (ambos están impresos en negro y no en negro-rojo como de costumbre). – Ivan

8

El problema es que es la responsabilidad del emulador de terminal (en su caso, Eclipse) procesar la salida estándar y el error estándar de su aplicación. Sin comunicarse con el emulador de terminal, nunca puede estar seguro de que out y err se muestran en el orden correcto. Por lo tanto, consideraría imprimir todo en err y redirigirlo a un archivo. Todavía puede usar out para una interacción limpia del usuario.

Sin embargo, hay una solución (muy malo, pero estricto) a su problema:

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

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

Puede que tenga que cambiar la duración del sueño en función de su configuración!

+0

¿Por qué esta solución es muy mala? –

2

Sé que esta publicación es antigua, pero sigue siendo un problema hoy, así que aquí hay otra solución que soluciona el problema utilizando la respuesta de @EserAygün, pero de una manera que no requiere que busque y modifique cada lugar de su proyecto donde está escribiendo al System.out o al System.err.

mismo crea una clase llamada EclipseTools con el siguiente contenido (y la declaración del paquete requerido y las importaciones):

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))); 
    } 
} 

Entonces, simplemente llame a EclipseTools.fixConsole() una vez en el comienzo de su código. Problema resuelto.

Básicamente, esto reemplaza las dos corrientes System.err y System.out con un conjunto personalizado de secuencias que simplemente reenvían sus datos a las secuencias originales, pero realiza un seguimiento de la secuencia que se escribió para durar. Si la secuencia que se escribe en los cambios, por ejemplo, System.err.something(...), seguido de System.out.something(...), vacía la salida de la última secuencia y espera 200 ms para darle tiempo a la consola Eclipse para completar la impresión.

Nota: Los 200ms tienen un valor inicial aproximado. Si este código reduce, pero no elimina el problema para usted, aumente la demora en Thread.sleep de 200 a algo más alto hasta que funcione. Alternativamente, si este retraso funciona pero afecta el rendimiento de tu código (si alternas las transmisiones a menudo), puedes intentar reducirlo gradualmente hasta que comiences a recibir errores.

0

El problema radica en el uso de la Consola Eclipse. Por lo general, std out escribirá los bytes uno a la vez en la consola, y std err también, pero en rojo. Sin embargo, el método no espera que los bytes se escriban antes de volver. Por lo tanto, lo que yo recomiendo es la siguiente:

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'); 
     } 
    } 
} 

Uso Printer.out y Printer.err en lugar de System.out y System.err. Todavía tiene los mismos errores, pero esto funciona mucho mejor.

Cuestiones relacionadas