Redirect System.out 和 System.err

2022-09-03 05:14:38

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

同时,我们正在使用一个框架,该框架使用围绕log4j的自定义日志记录系统(不幸的是,我们无法控制这一点)。

因此,我正在尝试将 out 和 err 流重定向到将使用日志记录系统的自定义 PrintStream。我正在阅读有关 和 的方法,但问题是我需要编写自己的 PrintStream 类,该类将包装在使用的日志记录系统周围。这将是一个非常令人头疼的问题。System.setLog()System.setErr()

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


答案 1

为了补充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,该 PrintStream 包装 CustomOutputStream 并将输出流设置为指向它。然后它执行一个 System.out.println() 语句。此语句被重定向到 CustomOutputStream,后者将其重定向到记录器。不幸的是,由于记录器是延迟初始化的,它将获取控制台输出流的副本(根据定义控制台Appender的log4j配置文件),即输出流将指向我们刚刚创建的CustomOutputStream,从而导致重定向循环,从而在运行时产生StackOverflowError。

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


答案 2

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


推荐