14

我有一些遗留代码(或者更确切地说是一些我们无法控制但我们必须使用的代码)将大量语句写入 system.out/err。

同时,我们正在使用一个框架,该框架使用一个围绕 log4j 的自定义日志系统(再次遗憾的是,我们无法控制它)。

因此,我试图将输出和错误流重定向到将使用日志记录系统的自定义 PrintStream。我正在阅读System.setLog()andSystem.setErr()方法,但问题是我需要编写自己的 PrintStream 类来包装正在使用的日志系统。那将是一个巨大的头痛。

有没有一种简单的方法可以实现这一目标?

4

4 回答 4

14

只是为了添加 Rick 和 Mikhail 的解决方案,这实际上是这种情况下的唯一选择,我想举一个例子来说明创建自定义 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);
  }
}

This example shows the pitfalls of using a custom output stream. For simplicity the write() function uses a log4j logger, but this can be replaced with any custom logging facility (such as the one in my scenario). The main function creates a PrintStream that wraps a CustomOutputStream and set the output stream to point to it. Then it executes a System.out.println() statement. This statement is redirected to the CustomOutputStream which redirects it to a logger. Unfortunately, since the logger is lazy initialized, it will acquire a copy of the console output stream (as per the log4j configuration file which defines a ConsoleAppender) too late, i.e., the output stream will point to the CustomOutputStream we just created causing a redirection loop and thus a StackOverflowError at runtime.

Now, with log4j this is easy to fix: we just need to initialize the log4j framework before we call System.setOut(), e.g., by uncommenting the first line of the main function. Luckily for me, the custom logging facility I have to deal with is just a wrapper around log4j and I know it will get initialized before it's too late. However, in the case of a totally custom logging facility that uses System.out/err under the cover, unless the source code is accessible, it's impossible to tell if and where direct calls to System.out/err are performed instead of calls to a PrintStream reference acquired during initialization. The only work around I can think of for this particular case would be to retrieve the function call stack and detect redirection loops, since the write() functions should not be recursive.

于 2013-02-11T17:00:33.647 回答
6

您不需要环绕您正在使用的自定义日志记录系统。当应用程序初始化时,只需创建一个 LoggingOutputStream 并在 System.setOut() 和 System.setErr() 方法上设置该流,并为其设置所需的日志记录级别。从那时起,应用程序中遇到的任何 System.out 语句都应该直接进入日志。

于 2013-02-05T20:18:14.600 回答
6

看看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 test -string 传递给 write-method,然后使用换行符再次调用它。

于 2013-02-05T20:21:55.267 回答
3

只需编写自己的 OutputStream,然后将其包装到标准 PrintStream 中。OutputStream 只有一个必须实现的方法,另一个出于性能原因值得重写。

唯一的技巧是将字节流分割成单独的日志消息,例如通过'\n',但这也不太难实现。

于 2013-02-05T20:17:55.103 回答