Перенаправить 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', но это не так уж сложно реализовать.

Другие вопросы по тегам