Перенаправить System.out и System.err
У меня есть какой-то устаревший код (или, вернее, некоторый код, который мы не контролируем, но мы должны использовать), который записывает много операторов в system.out / err.
В то же время мы используем фреймворк, в котором используется настраиваемая система ведения журнала, обернутая вокруг log4j (опять же, к сожалению, мы не контролируем это).
Поэтому я пытаюсь перенаправить поток out и err в пользовательский PrintStream, который будет использовать систему ведения журнала. Я читал о System.setLog()
а также System.setErr()
методы, но проблема в том, что мне нужно написать свой собственный класс PrintStream, который оборачивается вокруг используемой системы ведения журналов. Это было бы огромной головной болью.
Есть ли простой способ добиться этого?
4 ответа
Просто чтобы добавить к решениям Рика и Михаила, которые на самом деле являются единственным вариантом в этом сценарии, я хотел бы привести пример того, как создание собственного OutputStream потенциально может привести к не так просто обнаружить / исправить проблемы. Вот некоторый код:
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import org.apache.log4j.Logger;
public class RecursiveLogging {
/**
* log4j.properties file:
*
* log4j.rootLogger=DEBUG, A1
* log4j.appender.A1=org.apache.log4j.ConsoleAppender
* log4j.appender.A1.layout=org.apache.log4j.PatternLayout
* log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
*
*/
public static void main(String[] args) {
// Logger.getLogger(RecursiveLogging.class).info("This initializes log4j!");
System.setOut(new PrintStream(new CustomOutputStream()));
System.out.println("This message causes a stack overflow exception!");
}
}
class CustomOutputStream extends OutputStream {
@Override
public final void write(int b) throws IOException {
// the correct way of doing this would be using a buffer
// to store characters until a newline is encountered,
// this implementation is for illustration only
Logger.getLogger(CustomOutputStream.class).info((char) b);
}
}
Этот пример показывает ловушки использования пользовательского потока вывода. Для простоты функция write() использует регистратор log4j, но его можно заменить любым пользовательским средством ведения журналов (например, в моем сценарии). Основная функция создает PrintStream, который оборачивает CustomOutputStream и устанавливает выходной поток, указывающий на него. Затем он выполняет оператор System.out.println(). Этот оператор перенаправляется в CustomOutputStream, который перенаправляет его в журнал. К сожалению, поскольку регистратор инициализируется лениво, он получит копию выходного потока консоли (в соответствии с файлом конфигурации log4j, который определяет ConsoleAppender) слишком поздно, т. Е. Выходной поток будет указывать на только что созданный нами CustomOutputStream, вызывая перенаправление цикл и, следовательно, StackruError во время выполнения.
Теперь с log4j это легко исправить: нам просто нужно инициализировать каркас log4j, прежде чем мы вызовем System.setOut(), например, раскомментировав первую строку основной функции. К счастью для меня, пользовательские средства ведения журналов, с которыми мне приходится иметь дело, - это просто обертка вокруг log4j, и я знаю, что она будет инициализирована, пока не станет слишком поздно. Однако, в случае полностью настраиваемого средства ведения журнала, которое использует System.out / err под прикрытием, если исходный код не доступен, невозможно сказать, если и где прямые вызовы System.out / err выполняются вместо вызовов к ссылка PrintStream, полученная во время инициализации. Единственный обходной путь, который я могу придумать для этого конкретного случая, - это извлечь стек вызовов функций и обнаружить циклы перенаправления, поскольку функции write() не должны быть рекурсивными.
Вам не нужно оборачиваться вокруг пользовательской системы ведения журнала, которую вы используете. Когда приложение инициализируется, просто создайте LoggingOutputStream и установите этот поток для методов System.setOut() и System.setErr() с уровнем ведения журнала, который вы хотите для каждого. С этого момента любые операторы System.out, встречающиеся в приложении, должны идти непосредственно в журнал.
Посмотрите на конструкторы, предоставляемые PrintStream. Вы можете передать каркас ведения журнала OutputStream непосредственно в PrintStream, а затем настроить System.out и System.err для использования PrintStream.
Изменить: Вот простой тестовый пример:
public class StreamTest
{
public static class MyOutputStream extends FilterOutputStream
{
public MyOutputStream(OutputStream out)
{
super(out);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
byte[] text = "MyOutputStream called: ".getBytes();
super.write(text, 0, text.length);
super.write(b, off, len);
}
}
@Test
public void test()
{
//Any OutputStream-implementation will do for PrintStream, probably
//you'll want to use the one from the logging framework
PrintStream outStream = new PrintStream(new MyOutputStream(System.out), true); //Direct to MyOutputStream, autoflush
System.setOut(outStream);
System.out.println("");
System.out.print("TEST");
System.out.println("Another test");
}
}
Выход:
MyOutputStream called:
MyOutputStream called: TESTMyOutputStream called: Another testMyOutputStream called:
Во второй строке есть "пустой" вызов (MyOutputStream с именем: -output, а затем ничего после него), потому что println сначала передаст строку теста Another в метод write, а затем снова вызовет ее с переводом строки.
Просто напишите свой собственный OutputStream и затем оберните его в стандартный PrintStream. OutputStream имеет только один метод, который должен быть реализован, и другой, который стоит переопределить по соображениям производительности.
Единственная хитрость состоит в том, чтобы разделить поток байтов на отдельные сообщения журнала, например, с помощью '\n', но это не так уж сложно реализовать.