log4j将stdoutredirect到DailyRollingFileAppender

我有一个使用log4j的Java应用程序。

configuration:

log4j.rootLogger=info, file log4j.appender.file=org.apache.log4j.DailyRollingFileAppender log4j.appender.file.File=${user.home}/logs/app.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d [%t] %c %p %m%n 

所以所有的日志语句都正确地附加到文件,但我失去了stdout和stderr。 如何将exception堆栈跟踪和sysoutredirect到每日滚动文件?

 // I set up a ConsoleAppender in Log4J to format Stdout/Stderr log4j.rootLogger=DEBUG, CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=[%t] %-5p %c - %m%n // And I call this StdOutErrLog.tieSystemOutAndErrToLog() on startup public class StdOutErrLog { private static final Logger logger = Logger.getLogger(StdOutErrLog.class); public static void tieSystemOutAndErrToLog() { System.setOut(createLoggingProxy(System.out)); System.setErr(createLoggingProxy(System.err)); } public static PrintStream createLoggingProxy(final PrintStream realPrintStream) { return new PrintStream(realPrintStream) { public void print(final String string) { realPrintStream.print(string); logger.info(string); } }; } } 

在Skaffman代码中:要删除log4j日志中的空行,只需将“println”方法添加到createLoggingProxy的PrintStream

 public static PrintStream createLoggingProxy(final PrintStream realPrintStream) { return new PrintStream(realPrintStream) { public void print(final String string) { logger.warn(string); } public void println(final String string) { logger.warn(string); } }; } 

我从迈克尔·S(Michael S.)那里拿到了这个想法,但是像一个评论中提到的那样,它有一些问题:它不能捕捉所有的东西,而是打印出一些空行。

另外我想分离System.outSystem.err ,以便System.out以日志级别'INFO'进行logging,并且System.err'ERROR' (如果您喜欢,则为'WARN' )logging。

所以这是我的解决scheme:首先是扩展OutputStream的类(覆盖OutputStream所有方法比PrintStream更容易)。 它以指定的日志级别进行logging,并将所有内容复制到另一个OutputStream 。 它还检测“空”string(仅包含空白),并不会logging它们。

 import java.io.IOException; import java.io.OutputStream; import org.apache.log4j.Level; import org.apache.log4j.Logger; public class LoggerStream extends OutputStream { private final Logger logger; private final Level logLevel; private final OutputStream outputStream; public LoggerStream(Logger logger, Level logLevel, OutputStream outputStream) { super(); this.logger = logger; this.logLevel = logLevel; this.outputStream = outputStream; } @Override public void write(byte[] b) throws IOException { outputStream.write(b); String string = new String(b); if (!string.trim().isEmpty()) logger.log(logLevel, string); } @Override public void write(byte[] b, int off, int len) throws IOException { outputStream.write(b, off, len); String string = new String(b, off, len); if (!string.trim().isEmpty()) logger.log(logLevel, string); } @Override public void write(int b) throws IOException { outputStream.write(b); String string = String.valueOf((char) b); if (!string.trim().isEmpty()) logger.log(logLevel, string); } } 

然后是一个非常简单的实用程序类来设置和err

 import java.io.PrintStream; import org.apache.log4j.Level; import org.apache.log4j.Logger; public class OutErrLogger { public static void setOutAndErrToLog() { setOutToLog(); setErrToLog(); } public static void setOutToLog() { System.setOut(new PrintStream(new LoggerStream(Logger.getLogger("out"), Level.INFO, System.out))); } public static void setErrToLog() { System.setErr(new PrintStream(new LoggerStream(Logger.getLogger("err"), Level.ERROR, System.err))); } } 

如果您正在使用应用程序服务器,servlet容器或类似的东西,请参阅kgiannakakis的答案 。

对于独立的应用程序看到这一点 。 你可以使用java.lang.System类来强调stdin , stdout和stderr 。 基本上你创build一个新的PrintStream的子类,并将该实例设置为System.out。

在你的应用程序的开始沿着这些线(未经testing)。

 // PrintStream object that prints stuff to log4j logger public class Log4jStream extends PrintStream { public void write(byte buf[], int off, int len) { try { // write stuff to Log4J logger } catch (Exception e) { } } } // reassign standard output to go to log4j System.setOut(new Log4jStream()); 

我认为你通过e.printStackTrace()logging堆栈e.printStackTrace() ? 您可以将一个exception对象传递给Log4j日志logging方法,这些exception对象将出现在您的日志中(请参阅Logger.error(Object obj,Throwable t) )

请注意,您可以将System.out和System.err更改为redirect到Log4j的另一个PrintStream 。 这将是一个简单的改变,并保存转换所有的System.out.println()语句。

上面的答案给出了使用代理进行STDOUT / ERR日志的一个很好的想法。 但是,提供的实现示例并不适用于所有情况。 例如,尝试

System.out.printf(“Testing%s \ n”,“ABC”);

上面的例子中的代码会将输出剪切成控制台上的单独的部分,并且会在多个不可读的Log4j条目中出现。

解决的办法是缓冲输出,直到在缓冲区结尾find触发'\ n'。 有时缓冲区以'\ r \ n'结尾。 下面的课程解决了这个问题。 它function齐全。 调用静态方法bind()来激活它。

 import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import org.apache.log4j.Level; import org.apache.log4j.Logger; // Based on // http://stackoverflow.com/questions/1200175/log4j-redirect-stdout-to-dailyrollingfileappender public class Log4jStdOutErrProxy { public static void bind() { bind(Logger.getLogger("STDOUT"), Logger.getLogger("STDERR")); } @SuppressWarnings("resource") public static void bind(Logger loggerOut, Logger loggerErr) { System.setOut(new PrintStream(new LoggerStream(loggerOut, Level.INFO, System.out), true)); System.setErr(new PrintStream(new LoggerStream(loggerErr, Level.ERROR, System.err), true)); } private static class LoggerStream extends OutputStream { private final Logger logger; private final Level logLevel; private final OutputStream outputStream; private StringBuilder sbBuffer; public LoggerStream(Logger logger, Level logLevel, OutputStream outputStream) { this.logger = logger; this.logLevel = logLevel; this.outputStream = outputStream; sbBuffer = new StringBuilder(); } @Override public void write(byte[] b) throws IOException { doWrite(new String(b)); } @Override public void write(byte[] b, int off, int len) throws IOException { doWrite(new String(b, off, len)); } @Override public void write(int b) throws IOException { doWrite(String.valueOf((char)b)); } private void doWrite(String str) throws IOException { sbBuffer.append(str); if (sbBuffer.charAt(sbBuffer.length() - 1) == '\n') { // The output is ready sbBuffer.setLength(sbBuffer.length() - 1); // remove '\n' if (sbBuffer.charAt(sbBuffer.length() - 1) == '\r') { sbBuffer.setLength(sbBuffer.length() - 1); // remove '\r' } String buf = sbBuffer.toString(); sbBuffer.setLength(0); outputStream.write(buf.getBytes()); outputStream.write('\n'); logger.log(logLevel, buf); } } } // inner class LoggerStream } 

标准输出和错误stream由您的容器pipe理。 例如,Tomcat使用JULI来logging输出和错误stream。

我的build议是保持原样。 避免在应用程序中使用System.out.print。 看到这里的堆栈跟踪。

@迈克尔的angular色是一个好点。 但是扩展PrintStream不是很好,因为它使用内部方法void write(String)将所有内容写入OutputStream。

我更喜欢使用Log4J Contrib包中的LoggingOutputStream类 。

然后我像这样redirect系统stream:

 public class SysStreamsLogger { private static Logger sysOutLogger = Logger.getLogger("SYSOUT"); private static Logger sysErrLogger = Logger.getLogger("SYSERR"); public static final PrintStream sysout = System.out; public static final PrintStream syserr = System.err; public static void bindSystemStreams() { // Enable autoflush System.setOut(new PrintStream(new LoggingOutputStream(sysOutLogger, LogLevel.INFO), true)); System.setErr(new PrintStream(new LoggingOutputStream(sysErrLogger, LogLevel.ERROR), true)); } public static void unbindSystemStreams() { System.setOut(sysout); System.setErr(syserr); } } 

在使用System.setOut和System.setErr方法之前,我们应该使用reset()方法重置java.util.logging.LogManager对象。

 public static void tieSystemOutAndErrToLog() { try{ // initialize logging to go to rolling log file LogManager logManager = LogManager.getLogManager(); logManager.reset(); // and output on the original stdout System.out.println("Hello on old stdout"); System.setOut(createLoggingProxy(System.out)); System.setErr(createLoggingProxy(System.err)); //Following is to make sure whether system.out and system.err is redirecting to the stdlog.log file System.out.println("Hello world!"); try { throw new RuntimeException("Test"); } catch (Exception e) { e.printStackTrace(); } }catch(Exception e){ logger.error("Caught exception at StdOutErrLog ",e); e.printStackTrace(); } }