2010-11-25 25 views
12

J'utilise une bibliothèque rémanente qui, malheureusement, imprime des informations à System.out (ou occasionnellement System.err). Quel est le moyen le plus simple d'empêcher cela?"Éteindre" le flux de sortie

Je pensais à créer un flux de sortie en mémoire, remplacez System.out et err avant chaque appel à l'une des méthodes de résolution de problèmes, restaurez-les plus tard, et ignorez simplement le tampon du flux créé. Y a-t-il une manière plus facile, plus élégante?

EDIT: Je ne veux pas rediriger toutes les sorties - c'est facile à faire. Je veux seulement ignorer la sortie potentiellement générée par certains appels de bibliothèque.

Répondre

16

je fini par faire quelque chose comme:

PrintStream out = System.out; 
System.setOut(new PrintStream(new OutputStream() { 
    @Override public void write(int b) throws IOException {} 
})); 
try { 
    <library call> 
} finally { 
    System.setOut(out); 
} 

Merci à Alexr et stacker pour me rediriger vers cette courte solution.

1
File file = new File(filename); 
PrintStream printStream = new PrintStream(new FileOutputStream(file)); 
System.setOut(printStream); 
+0

Alors juste le vider dans un fichier aléatoire? Et plus tard supprimer le fichier? Cela semble un peu inutile, n'est-il pas préférable de le vider dans un tampon en mémoire? – Oak

+0

@Oak Si les messages ne vous intéressent pas, que diriez-vous d'étendre PrintStream et d'ignorer quoi que ce soit (implémentations vides)? – stacker

+0

Sous-classer avec une implémentation vide est en fait la meilleure idée à ce jour - très simple, assez court avec une classe anonyme. – Oak

1

Il existe 2 façons. 1. Le moyen le plus simple est d'exécuter votre programme et de rediriger toutes les sorties vers/dev/null (si vous êtes sur unix) ou vers un fichier spécial qui sera supprimé (pour Windows) Cette façon est simple mais cela signifie que tous vos la sortie ne sert à rien. Si votre programme utilise STDOUT pour de bonnes choses, vous ne pouvez pas utiliser de cette façon.

  1. Vous pouvez définir STDOUT à l'aide de l'API java.

    System.setOut (out) System.setErr (out)

Maintenant, vous pouvez mettre en œuvre votre propre OutputStream:

import java.io.IOException; 
import java.io.OutputStream; 
import java.util.regex.Pattern; 


public class FilterOutputStream extends OutputStream { 
    private OutputStream out; 
    private Pattern filterOut; 
    public FilterOutputStream(OutputStream out, Pattern filterOut) { 
     this.out = out; 
     this.filterOut = filterOut; 
    } 


    @Override 
    public void write(int b) throws IOException { 
     String callerClassName = new Throwable().getStackTrace()[1].getClassName(); 
     if (filterOut.matcher(callerClassName).find()) { 
      return; 
     } 
     out.write(b); 
    } 

} 

C'est mieux parce que vous pouvez filtrer la sortie hors de propos et imprimez une bonne information.

+2

L'envoi de la sortie vers/dev/null est une mauvaise idée car cela forcerait un changement de contexte et aurait un impact très sérieux sur performace. Je l'apprends à la dure dans un centre de référence. – stacker

+1

La création de la trace de pile d'une exception est lente, vous devez donc profiler l'application si vous utilisez ce code. L'appel de 'new Throwable()' peut facilement devenir un goulot d'étranglement si le programme imprime quelque chose pendant le fonctionnement normal. De plus, vous ne pouvez pas savoir lequel des éléments trace de la pile a été appelé, car le nombre de cadres de pile dépend de la méthode d'écriture qui a été appelée à l'origine, donc une certaine itération est nécessaire. –

4

Une façon de se débarrasser de ces impressions non désirées de façon permanente serait d'utiliser la manipulation de bytecode pour supprimer les instructions d'impression de la bibliothèque gênante. Cela peut être fait par exemple en utilisant ASM (ou l'un des autres cadres de niveau supérieur et plus facile à utiliser AOP).

Vous pouvez le faire au moment de l'exécution ou en tant qu'opération unique de réécriture des fichiers de classe de la bibliothèque. Reportez-vous à la documentation d'ASM pour savoir comment. Voici une preuve de concept. Qu'est-ce qu'il fait, c'est qu'il remplace toutes les références à System.out avec une référence à un PrintStream qui ne fait rien.

D'abord les tests. Ils utilisent certaines classes d'utilitaires de my project pour aider à tester les transformations bytecode (le tester nécessite de créer un chargeur de classes personnalisé et d'appliquer les transformations bytecode à la bonne classe, mais pas aux autres classes).

package net.orfjackal.dimdwarf.aop; 

import net.orfjackal.dimdwarf.aop.conf.*; 
import org.junit.*; 
import org.objectweb.asm.*; 
import org.objectweb.asm.util.CheckClassAdapter; 

import java.io.*; 
import java.lang.instrument.ClassFileTransformer; 
import java.lang.reflect.*; 

import static org.hamcrest.MatcherAssert.assertThat; 
import static org.hamcrest.Matchers.*; 

public class RemoveCallsToSystemOutTest { 

    private PrintStream originalOut; 
    private ByteArrayOutputStream collectedOut; 

    @Before 
    public void collectSystemOut() { 
     originalOut = System.out; 
     collectedOut = new ByteArrayOutputStream(); 
     System.setOut(new PrintStream(collectedOut)); 
    } 

    @After 
    public void restoreOriginalSystemOut() { 
     System.setOut(originalOut); 
    } 

    @Test 
    public void the_target_class_prints_when_not_manipulated() throws Exception { 
     String safetyCheck = callPrintSomething(TroublesomePrinter.class); 

     assertThat(safetyCheck, is("it did execute")); 
     assertThat(collectedOut.size(), is(greaterThan(0))); 
    } 

    @Test 
    public void the_target_class_does_not_print_when_it_has_been_manipulated() throws Exception { 
     String safetyCheck = callPrintSomething(instrumentClass(TroublesomePrinter.class)); 

     assertThat(safetyCheck, is("it did execute")); 
     assertThat(collectedOut.size(), is(0)); 
    } 

    private static String callPrintSomething(Class<?> clazz) throws Exception { 
     Method m = clazz.getMethod("printSomething"); 
     m.setAccessible(true); 
     return (String) m.invoke(null); 
    } 

    private static Class<?> instrumentClass(Class<?> cls) throws ClassNotFoundException { 
     ClassFileTransformer transformer = new AbstractTransformationChain() { 
      protected ClassVisitor getAdapters(ClassVisitor cv) { 
       cv = new CheckClassAdapter(cv); 
       cv = new RemoveCallsToSystemOut(cv); 
       return cv; 
      } 
     }; 
     ClassLoader loader = new TransformationTestClassLoader(cls.getPackage().getName() + ".*", transformer); 
     return loader.loadClass(cls.getName()); 
    } 
} 

class TroublesomePrinter { 
    public static String printSomething() { 
     System.out.println("something"); 
     return "it did execute"; 
    } 
} 

Et puis l'implémentation. Veuillez noter que vous ne devez pas utiliser ce code sans le comprendre au préalable. Ne pas program by coincidence.

class SilentSystem { 
    public static final PrintStream out = new PrintStream(new OutputStream() { 
     public void write(int b) { 
     } 
    }); 
} 

class RemoveCallsToSystemOut extends ClassAdapter { 

    public RemoveCallsToSystemOut(ClassVisitor cv) { 
     super(cv); 
    } 

    @Override 
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 
     return new MyMethodAdapter(super.visitMethod(access, name, desc, signature, exceptions)); 
    } 

    private static class MyMethodAdapter extends MethodAdapter { 
     public MyMethodAdapter(MethodVisitor mv) { 
      super(mv); 
     } 

     @Override 
     public void visitFieldInsn(int opcode, String owner, String name, String desc) { 
      if (opcode == Opcodes.GETSTATIC 
        && owner.equals("java/lang/System") 
        && name.equals("out") 
        && desc.equals("Ljava/io/PrintStream;")) { 
       super.visitFieldInsn(opcode, "net/orfjackal/dimdwarf/aop/SilentSystem", name, desc); 
      } else { 
       super.visitFieldInsn(opcode, owner, name, desc); 
      } 
     } 
    } 
} 
+0

Je cherchais une méthode * simple * :) encore, merci, c'est très intéressant. – Oak

+0

Quelque chose que je n'ai jamais su pourrait être fait, merci. – Sid

+0

La simplicité peut dépendre du point de vue. ;) Cette méthode est sûrement avancée, mais après avoir traité le JAR une fois, la bibliothèque sera très simple à utiliser. L'autre méthode d'utilisation de System.setOut, d'autre part, est plus facile à apprendre, mais peut provoquer un comportement inattendu à l'exécution; il peut facilement empêcher l'impression de certaines choses que vous voudriez imprimer, et il y a beaucoup de cas à prendre en compte, tels que la concurrence et l'impression de plusieurs threads, les exceptions lancées par la bibliothèque, ou si vous voulez cacher seulement certains les choses imprimées par la bibliothèque et montrer aux autres. –