log4j将标准输出重定向到DailyRollingFileAppender


67

我有一个使用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。如何将异常堆栈跟踪和sysouts重定向到每日滚动文件?

Answers:


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

7
这适用于例外情况,但不能捕获所有写入到stdout的输出。例如,如果您调用System.out.print(5); 它将绕过代理打印流。同样,即使有例外,如果执行someException.printStackTrace()
最终也会

3
@sbridges可以根据需要覆盖更多方法。例如,printStackTrace调用该println(Object o)方法,您也可以重写该方法以消除那些讨厌的空行。
tysonjh 2013年

在检查您的代码时,我学到的不仅仅是日志。辉煌!谢谢。
Lalith J.

这是否适用于整个Tomcat实例或仅适用于具有此类的Web应用程序?
2014年

如果使用此方法,我认为最好在PrintStream中实现所有方法。如果您想变得懒惰,可以使用实现输出流的Dario答案。请注意我在评论中提到的那种方法的警告。
Mike Pone 2014年

12

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

10

我从Michael S.提出了这个想法,但是就像在一条评论中提到的那样,它存在一些问题:它无法捕获所有内容,并且会打印一些空白行。

我也想分开System.outSystem.err,以便System.out使用日志级别记录日志,'INFO'System.err使用'ERROR'(或者,'WARN'如果愿意)进行日志记录。

所以这是我的解决方案:首先是一个扩展类OutputStream(覆盖for的所有方法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);
}
}

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

}

将路径添加到日志文件的位置
Aniket 2012年

您能给我一个关于log4j.properties或log4j.xml文件如何工作的想法吗?
Aniket 2012年

@acoolguy请参阅Log4j XML配置入门,使用FileAppender
Dario Seidl 2012年

1
此实现的唯一缺点是,如果将堆栈跟踪打印到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] INFO {ERR} at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinition‌​s(XmlBeanDefinitionReader.java:334)
Mike Pone 2014年

6

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

对于独立的应用程序见。您可以使用java.lang.System类重新设置stdinstdoutstderr。基本上,您将创建一个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());

4

上面的答案给出了一个很好的想法,可以将代理用于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  

}

3

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

需要包括log4j-iostreams jar
参见: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);

2

我想您是通过e.printStackTrace()?记录stacktrace的 您可以将异常对象传递到Log4j日志记录方法中,这些方法将出现在日志中(请参阅Logger.error(Object obj,Throwable t)

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


1

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

我的建议是保留这些原样。避免在应用程序中使用System.out.print。有关堆栈跟踪,请参见此处


1
不使用容器的独立Java应用程序怎么样?当然有办法捕获stdout并将其记录到log4j吗?
JuhaSyrjälä09年

根据Brian Agnew的建议,您可以将流重定向到log4j。但是我建议反对。解决方案不是在标准输出上打印,而是使用log4j。没有一个使用标准输出的像样的Java库。如果您有执行此操作的第三方代码,请要求他们使用log4j或commons日志代替。重定向标准输出应该是最后的选择。
kgiannakakis 2009年

不要使用System.out.println()进行日志记录。在代码中使用log4j(或其他日志记录框架),只需使配置将输出发送到stdout。
马特b

2
哦,男孩;-) assumably,整点是有关捕获第三方打印到System.out -例如,如果你打开log4j.debug它会打印到System.out(见双对数)
约尔格

1

@Michael的回答很有趣。但是扩展PrintStream并不是很好,因为它使用内部方法void write(String)将所有内容写入OutputStream。

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

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

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

在最新的log4j contrib版本(1.0.4)中似乎不再存在LoggingOutputStream
Nicolas Mommaerts 2013年


-1

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

什么createLoggingProxy
WindyFields
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.