Log4j 将标准输出重定向到 DailyRollingFileAppender

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

配置:

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。如何将异常堆栈跟踪和系统解析重定向到每日滚动的文件?

95839 次浏览

我猜你是通过 e.printStackTrace()记录堆栈跟踪吧?您可以将一个异常对象传递给 Log4j 日志记录方法,这些方法将出现在您的日志中(参见 Error (Object obj,Throwable t))

注意,您可以将 System.out 和 System.err 更改为另一个重定向到 Log4j 的 PrintStream。这将是一个直接的变化,并保存您转换所有的 System.out.println()语句。

从容器中管理标准输出和错误流。例如,Tomcat 使用 JULI 记录输出和错误流。

我的建议是让它们保持原样。避免在应用程序中使用 System.out.print。请参阅 给你了解堆栈跟踪。

如果您正在使用应用程序服务器、 servlet 容器或类似的东西,请参见 Kgiannakakis 的回答

独立应用程序见 这个。您可以使用 Java.lang. 系统类重传 StdinStdout真是的。基本上,您创建了 PrintStream 的一个新子类,并将该实例设置为 System.out。

在你的应用程序开始的时候(未经测试)。

// 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());
// 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);
}
};
}
}

我从 Michael S 那里学到了这个想法,但是就像在一条评论中提到的,它有一些问题: 它不能捕捉所有的东西,而且它打印出一些空行。

此外,我想分开 System.outSystem.err,这样,System.out得到与日志级别 'INFO'System.err得到与 'ERROR'(或 'WARN',如果你喜欢)记录。

所以这就是我的解决方案: 首先是一个扩展 OutputStream的类(覆盖 OutputStream的所有方法比覆盖 PrintStream的所有方法更容易)。它使用指定的日志级别记录日志,并将所有内容复制到另一个 OutputStream。而且它还检测“空”字符串(仅包含空格) ,并且不记录它们。

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);
}
}

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

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)));
}


}

“迈克尔”的回答很有道理。但是扩展 PrintStream 并不好,因为它使用内部方法 void write(String)将所有内容写入 OutputStream。

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

然后我重定向系统流,像这样:

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);
}
}

在 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);
}
};
}

上面的答案为使用代理进行 STDOUT/ERR 日志记录提供了一个很好的想法。然而,提供的实现示例并不适用于所有情况。比如说,试试

Printf (“测试% s n”,“ ABC”) ;

上面示例中的代码将输出切割成控制台上的单独块和多个不可读的 Log4j 条目。

解决方案是对输出进行缓冲,直到在缓冲器的末端找到触发器 n’为止。有时候缓冲区以“ n”结尾。 下面的类解决了这个问题。它是完全可用的。调用静态方法 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


}

在使用 System.setOut 和 System.setErr 方法之前,我们应该通过复位()方法重置 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();
}
}

对于那些寻找如何在 log4j2中做到这一点。现在有一个组件可以为您创建这些流。

需要包括 log4j-iostream jar
参见: < a href = “ https://logging.apache.org/log4j/2.x/log4j-iostream/index.html”rel = “ norefrer”> https://logging.apache.org/log4j/2.x/log4j-iostreams/index.html

例如:

PrintStream logger = IoBuilder.forLogger("System.out").setLevel(Level.DEBUG).buildPrintStream();
PrintStream errorLogger = IoBuilder.forLogger("System.err").setLevel(Level.ERROR).buildPrintStream();
System.setOut(logger);
System.setErr(errorLogger);