Подтвердить что ты не робот

Перенаправление System.out и System.err

У меня есть некоторый код устаревшего кода (или, скорее, некоторый код, который мы не контролируем, но мы должны использовать), который записывает множество инструкций в system.out/err.

В то же время мы используем фреймворк, который использует собственную систему ведения журналов, обернутую вокруг log4j (опять же, к сожалению, мы этого не контролируем).

Итак, я пытаюсь перенаправить поток out и err в настраиваемый PrintStream, который будет использовать систему ведения журнала. Я читал о методах System.setLog() и System.setErr(), но проблема в том, что мне нужно будет написать собственный класс PrintStream, который обернется вокруг используемой системы ведения журнала. Это будет огромная головная боль.

Есть ли простой способ достичь этого?

4b9b3361

Ответ 1

Просто чтобы добавить к решениям Рика и Михаэля, которые на самом деле являются единственным вариантом в этом сценарии, я хотел бы привести пример того, как создание пользовательского 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 Qaru 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 logger, но это может быть заменено любым настраиваемым средством ведения журнала (например, в моем сценарии). Основная функция создает PrintStream, который обертывает CustomOutputStream и устанавливает выходной поток для указания на него. Затем он выполняет инструкцию System.out.println(). Этот оператор перенаправляется на CustomOutputStream, который перенаправляет его на регистратор. К сожалению, поскольку логгер инициализирован ленивым, он будет получать копию выходного потока консоли (в соответствии с конфигурационным файлом log4j, который определяет ConsoleAppender) слишком поздно, т.е. Выходной поток будет указывать на только что созданный CustomOutputStream, вызывающий перенаправление и, таким образом, StackOverflowError во время выполнения.

Теперь, с log4j, это легко исправить: нам просто нужно инициализировать структуру log4j до того, как мы позвоним System.setOut(), например, раскомментируя первую строку основной функции. К счастью для меня, пользовательский механизм ведения журнала, с которым мне приходится иметь дело, - это всего лишь обертка вокруг log4j, и я знаю, что он будет инициализирован до того, как будет слишком поздно. Однако в случае полностью настраиваемого средства ведения журнала, использующего System.out/err под обложкой, если только исходный код не доступен, невозможно определить, будут ли выполняться прямые вызовы System.out/err вместо вызовов на ссылка на PrintStream, полученная во время инициализации. Единственная работа, о которой я могу думать в этом конкретном случае, - это получить стек вызовов функций и обнаружить петли перенаправления, поскольку функции write() не должны быть рекурсивными.

Ответ 2

Вам не нужно обертывать пользовательскую систему ведения журнала, которую вы используете. Когда приложение инициализируется, просто создайте LoggingOutputStream и установите этот поток в методах System.setOut() и System.setErr() с уровнем ведения журнала, который вы желаете для каждого. С этого момента любые заявления System.out, встречающиеся в приложении, должны перейти непосредственно в журнал.

Ответ 3

Взгляните на конструкторы, предоставленные 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 сначала передаст Еще один тест -струмент для метода write, а затем снова вызывает его с помощью строки.

Ответ 4

Просто напишите свой собственный OutputStream, а затем оберните его в стандартный PrintStream. OutputStream имеет только один метод, который должен быть реализован, а другой, который стоит переопределить по соображениям производительности.

Единственный трюк состоит в том, чтобы разрезать поток байтов на отдельные сообщения журнала, например. по '\n', но это тоже не слишком сложно реализовать.