log4j将stdout重定向到DailyRollingFileAppender

let*_*nje 64 java redirect log4j stdout file

我有一个使用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
Run Code Online (Sandbox Code Playgroud)

所以所有日志语句都正确地附加到文件中,但我丢失了stdout和stderr.如何将异常堆栈跟踪和sysout重定向到每日滚动文件?

小智 99

// 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);
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这适用于异常,但它不会捕获写入stdout的所有输出.例如,如果您调用System.out.print(5); 它会绕过代理打印流.同样,即使有例外情况,如果你执行someException.printStackTrace(),最终会向stderr打印额外的空白行. (7认同)
  • @sbridges如果需要,你可以覆盖更多的方法.例如,`printStackTrace`调用`println(Object o)`方法,您也可以覆盖它以消除那些令人讨厌的空白行. (3认同)

小智 11

在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);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)


Dar*_*idl 10

我从Michael S.那里得到了这个想法,但是就像在一条评论中提到的那样,它有一些问题:它没有捕获所有内容,而是打印出一些空行.

此外,我想单独System.outSystem.err,使System.out获取与日志记录级别'INFO'System.err获取与登录'ERROR'(或者'WARN',如果你喜欢).

所以这是我的解决方案:首先是一个扩展的类OutputStream(它更容易覆盖所有方法而OutputStream不是for 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);
}
}
Run Code Online (Sandbox Code Playgroud)

然后是一个非常简单的实用程序类来设置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)));
}

}
Run Code Online (Sandbox Code Playgroud)

  • 此实现的唯一缺点是,如果您将堆栈跟踪打印到 StdErr,它将一次打印一行,如下所示:`2014-12-22 19:55:04,581 [main] INFO {ERR} at org. springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefiniti‌​ons(XmlBeanDefinitionReader.java:396) 2014-12-22 19:55:04,581 [main] 信息 {ERR} 位于 org.springframework.beans.factory.xml.XmlBeanDefinitionReader。 loadBeanDefinition‌​s(XmlBeanDefinitionReader.java:334)` (2认同)

小智 7

上面的答案给出了使用代理进行 STDOUT/ERR 日志记录的好主意。然而,提供的实现示例并不适用于所有情况。例如,尝试

System.out.printf("测试 %s\n", "ABC");

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

解决方案是缓冲输出,直到在缓冲区末尾找到触发器“\n”。有时缓冲区以“\r\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  

}
Run Code Online (Sandbox Code Playgroud)


Row*_*wan 7

对于那些正在寻找如何在 log4j2 中执行此操作的人。现在有一个组件可以为您创建这些流。

需要包含 log4j-iostreams jar
请参阅: https: //logging.apache.org/log4j/2.x/log4j-iostreams.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);
Run Code Online (Sandbox Code Playgroud)


Juh*_*älä 6

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

对于独立应用,请看这个.您可以使用java.lang.System类重新生成stdin,stdoutstderr.基本上,您创建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());
Run Code Online (Sandbox Code Playgroud)