【问题标题】:log4j redirect stdout to DailyRollingFileAppenderlog4j 将标准输出重定向到 DailyRollingFileAppender
【发布时间】:2009-07-29 13:15:45
【问题描述】:

我有一个使用 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

所以所有日志语句都正确附加到文件中,但我丢失了标准输出和标准错误。如何将异常堆栈跟踪和系统输出重定向到每日滚动文件?

【问题讨论】:

    标签: java file redirect log4j stdout


    【解决方案1】:
    // 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);
                }
            };
        }
    }
    

    【讨论】:

    • 这适用于异常,但它不会捕获写入标准输出的所有输出。例如,如果您调用 System.out.print(5);它会绕过代理打印流。同样,即使有异常,如果您执行 someException.printStackTrace(),最终也会将额外的空白行打印到 stderr
    • @sbridges 如果需要,您可以覆盖更多方法。例如printStackTrace 调用println(Object o) 方法,您也可以重写该方法以消除那些讨厌的空行。
    • 在检查您的代码时,我学到的不仅仅是记录。杰出的!谢谢。
    • 这适用于整个 Tomcat 实例还是仅适用于具有此类的 Web 应用程序?
    • 如果你使用这种方法,我认为你最好实现PrintStream中的所有方法。如果你想偷懒,你可以使用实现 OutputStream 的达里奥的答案。请注意我在 cmets 中提到的那种方法的警告。
    【解决方案2】:

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

    【讨论】:

    • 在哪里添加日志文件的路径??
    • 你能给我一个关于 log4j.properties 或 log4j.xml 文件应该如何工作的想法吗?
    • 这个实现的唯一缺点是,如果你有一个堆栈跟踪打印到 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)
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      如果您使用的是应用服务器、servlet 容器或类似的东西,请参阅kgiannakakis's answer

      对于独立应用,请参阅this。您可以使用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());
      

      【讨论】:

        【解决方案5】:

        上面的答案给出了使用代理进行 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  
        
        }
        

        【讨论】:

          【解决方案6】:

          对于那些寻找如何在 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);
          

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              【解决方案8】:

              标准输出和错误流由您的容器管理。例如 Tomcat 使用 JULI 来记录输出和错误流。

              我的建议是保持原样。避免在您的应用程序中使用 System.out.print。有关堆栈跟踪,请参阅 here

              【讨论】:

              • 不使用容器的独立 Java 应用程序怎么样?当然有一种方法可以捕获标准输出并将其记录到 log4j 中?
              • 按照 Brian Agnew 的建议,您可以将流重定向到 log4j。然而,我建议反对它。解决方案不是在标准输出打印,而是使用 log4j。没有一个像样的 Java 库使用标准输出。如果您有执行此操作的第三方代码,请让他们使用 log4j 或 commons 日志记录。重定向标准输出应该是最后的手段。
              • 不要使用 System.out.println() 进行日志记录。在您的代码中使用 log4j(或其他日志框架),只需让配置将输出发送到标准输出。
              • 哦,男孩们 ;-) 大概,重点是捕获第三方打印到 System.out - 例如如果您打开 log4j.debug,它将打印到 System.out(另请参阅 LogLog)
              【解决方案9】:

              @Michael 的分析器是一个很好的观点。但是扩展 PrintStream 不是很好,因为它使用内部方法 void write(String) 将所有内容写入 OutputStream。

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

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

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

              【讨论】:

              【解决方案10】:

              在使用 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
              猜你喜欢
              • 1970-01-01
              • 2012-08-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-05-26
              • 2016-04-26
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多