"Отключить" выходной поток
Я использую своенравную библиотеку, которая, к сожалению, печатает информацию в System.out
(или иногда System.err
). Какой самый простой способ предотвратить это?
Я думал о создании потока вывода в память, заменяя System.out
и err
перед каждым вызовом на один из методов устранения неполадок, восстанавливая их позже и просто игнорируя буфер созданного потока. Есть ли более простой и элегантный способ?
EDIT: я не хочу перенаправлять весь вывод - это легко выполняется. Я просто хочу игнорировать вывод, потенциально генерируемый некоторыми вызовами библиотеки.
Ответы
Ответ 1
В итоге я сделал что-то вроде:
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);
}
Благодаря AlexR и укладчику для перенаправления меня на это короткое решение.
Ответ 2
Чтобы избавиться от этих нежелательных отпечатков навсегда, было бы использовать манипуляции с байт-кодом для удаления заявлений печати из неудобной библиотеки. Это можно сделать, например, используя ASM (или один из других более высоких уровней и более простой в использовании AOP).
Вы можете сделать это либо во время выполнения, либо в виде однократной операции перезаписи файлов классов библиотеки. Обратитесь к документации ASM, чтобы узнать, как это сделать. Вот доказательство концепции. Он делает то, что он заменяет все ссылки на System.out
ссылкой на PrintStream
, которая ничего не делает.
Сначала тесты. Они используют некоторые служебные классы из моего проекта, чтобы помочь в тестировании преобразований байт-кода (для тестирования требуется создание пользовательского загрузчика классов и применение преобразований байт-кода вправо класс, но не любые другие классы).
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";
}
}
И затем реализация. Обратите внимание, что вы не должны использовать этот код, не понимая его. Не выполняйте программу по совпадению.
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);
}
}
}
}
Ответ 3
File file = new File(filename);
PrintStream printStream = new PrintStream(new FileOutputStream(file));
System.setOut(printStream);
Ответ 4
Есть два способа.
1. Самый простой способ - запустить вашу программу и перенаправить весь вывод на /dev/null (если вы в unix) или на специальный файл, который будет удален (для Windows)
Этот способ прост, но это означает, что весь ваш выход ни к чему. Если ваша программа использует STDOUT для хороших вещей, вы не можете использовать этот способ.
Теперь вы можете реализовать свой собственный 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);
}
}
Это лучше, потому что вы можете отфильтровывать нерелевантный вывод и печатать хорошую информацию.