"Turn off" the output stream
Asked Answered
L

4

12

I'm using a wayward library that, unfortunately, prints information to System.out (or occasionally System.err). What's the simplest way to prevent this?

I've been thinking about creating an output stream to memory, replace System.out and err before every call to one of the troublemaking methods, restore them later, and just ignore the buffer of the created stream. Is there an easier, more elegant way?

EDIT: I don't want to redirect all output - that's easily accomplished. I only want to ignore output potentially generated by certain library calls.

Landsknecht answered 25/11, 2010 at 21:48 Comment(0)
L
16

I ended up doing something like:

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

Thanks to AlexR and stacker for redirecting me to this short solution.

Landsknecht answered 28/11, 2010 at 10:53 Comment(0)
S
4

A way to get rid of those unwanted prints permanently would be to use bytecode manipulation to remove the print statements from the troublesome library. This can be done for example using ASM (or one of the other higher level and easier to use AOP frameworks).

You can do this either at runtime or as a one-time operation of rewriting the library's class files. Refer to ASM's documentation to find out how. Here is a proof of concept. What it does is that it replaces all references to System.out with a reference to a PrintStream which does nothing.

First the tests. They use some utility classes from my project to help with testing bytecode transformations (testing it requires creating a custom class loader and applying the bytecode transformations to the right class but not any other 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";
    }
}

And then the implementation. Please note that you should not use this code without first understanding it. Do not 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);
            }
        }
    }
}
Serviceable answered 25/11, 2010 at 23:16 Comment(3)
I was looking for a simple method :) still, thank you, this is very interesting.Landsknecht
Simplicity may depend on the point of view. ;) This method is surely advanced, but after processing the JAR once, the library will be very simple to use. The other method of using System.setOut on the other hand is easier to learn, but can cause unexpected behaviour at runtime; it can easily prevent printing some things which you would like to be printed, and there are many corner cases to take into account, such as concurrency and printing from multiple threads, exceptions thrown by the library, or if you want to hide only some of the things printed by the library and show others.Serviceable
Oh yeah. If the library is open source, you have the option of removing the print statements from the source and recompiling it.Serviceable
P
1
File file  = new File(filename);
PrintStream printStream = new PrintStream(new FileOutputStream(file));
System.setOut(printStream);
Penultimate answered 25/11, 2010 at 22:6 Comment(3)
So just dump it into a random file? And later delete the file? Sounds a bit wasteful, isn't it better to dump it to an in-memory buffer?Landsknecht
@Landsknecht If you're not interested in the messages - how about to extend PrintStream and ignore anything (empty implementations)?Penultimate
Subclassing with an empty implementation is actually the best idea so far - very simple, pretty short with an anonymous class.Landsknecht
Z
1

There are 2 ways. 1. the simplest way is to run your program and redirect all output to /dev/null (if you are on unix) or to special file that will be removed (for windows) This way is simple but it means that all your output is going to nothing. If your program uses STDOUT for good things you cannot use this way.

  1. You can set STDOUT using java API.

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

Now you can implement your own 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);
    }

}

This is better because you can filter out irrelevant output and print good information.

Zulemazullo answered 25/11, 2010 at 22:13 Comment(2)
Sending output to /dev/null is a bad idea because it would force a context switch , and has a very serious impact on performace. I learend it the hardway in a benchmark center.Penultimate
Creating the stack trace of an exception is slow, so you should profile the application if you use that code. The call to new Throwable() can easily become a bottleneck if the program prints something during normal operation. Also you can't know that which of the stack trace elements was called, because the number of stack frames depends on which write method was originally called, so some iterating is needed.Serviceable

© 2022 - 2024 — McMap. All rights reserved.