重定向System.out和System.err

时间:2013-02-05 19:51:02

标签: java logging log4j

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

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

所以我试图将out和err流重定向到将使用日志记录系统的自定义PrintStream。我正在阅读System.setLog()System.setErr()方法,但问题是我需要编写自己的PrintStream类来包装正在使用的日志系统。那将是一个巨大的麻烦。

有没有一种简单的方法可以达到这个目的?

4 个答案:

答案 0 :(得分:13)

只是为了添加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);
  }
}

此示例显示使用自定义输出流的缺陷。为简单起见,write()函数使用log4j记录器,但这可以替换为任何自定义日志记录工具(例如我的方案中的那个)。 main函数创建一个PrintStream,它包装CustomOutputStream并将输出流设置为指向它。然后它执行System.out.println()语句。此语句将重定向到CustomOutputStream,后者将其重定向到记录器。不幸的是,由于记录器是惰性初始化的,它将获取控制台输出流的副本(根据定义ConsoleAppender的log4j配置文件)太晚,即输出流将指向我们刚刚创建的CustomOutputStream导致重定向循环,因此在运行时出现StackOverflowError。

现在,使用log4j这很容易解决:我们只需要在之前初始化log4j框架,我们调用System.setOut(),例如,通过取消注释main函数的第一行。幸运的是,我必须处理的自定义日志记录工具只是log4j的一个包装器,我知道它会在它为时已晚之前进行初始化。但是,对于在封面下使用System.out / err的完全自定义日志记录工具,除非可以访问源代码,否则无法判断是否以及在何处执行对System.out / err的直接调用而不是调用初始化期间获取的PrintStream引用。对于这种特殊情况,我能想到的唯一工作就是检索函数调用堆栈并检测重定向循环,因为write()函数不应该是递归的。

答案 1 :(得分:6)

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

答案 2 :(得分:4)

看一下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将首先通过   写入方法的另一个测试 - 字符串,然后使用换行符再次调用它。

答案 3 :(得分:3)

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

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