只是为了添加 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.