【问题标题】:Log4j2 RollingFile Appender - add custom info at the start of each logfileLog4j2 RollingFile Appender - 在每个日志文件的开头添加自定义信息
【发布时间】:2014-01-16 03:33:45
【问题描述】:

在 java 1.7 中使用 log4j2 (beta9)。

我的完整 log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Properties>
    <Property name="projectPrefix">Tts</Property>
    <Property name="rawPattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</Property>
    <Property name="coloredPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] %C{2} (%F:%L) -}{bright,black} %m%n</Property>
    <Property name="fileName">Log/${projectPrefix}.log</Property>
    <Property name="filePattern">Log/${projectPrefix}-%i.log</Property>
  </Properties>
  <Appenders>
    <Console name="Stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="${coloredPattern}"/>
    </Console>
    <RollingFile name="Logfile" fileName="${fileName}" filePattern="${filePattern}">
      <PatternLayout pattern="${rawPattern}"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="16 MB"/>
      </Policies>
      <DefaultRolloverStrategy fileIndex="min" max="16"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Stdout"/>
      <AppenderRef ref="Logfile"/>
    </Root>
  </Loggers>
</Configuration>

我想在每个日志文件的顶部添加一些自定义信息,例如我的应用程序的版本字符串、应用程序正常运行时间和系统正常运行时间。甚至在刚刚关闭的日志文件的底部写一些“再见,再见/eof”也可以。

当 RollingFileAppander 创建新文件时,是否有类似钩子或回调的东西来获得通知,以便我可以首先将我的东西放入这些新日志文件(或任何其他建议)?

【问题讨论】:

    标签: java log4j2


    【解决方案1】:

    好的,通过像here 中描述的那样扩展 DefaultRolloverStrategy 可以解决这个问题。但是

    • 它需要大约 150 行代码(包括包装 RolloverDescription 和 appender.rolling.helper.Action)和
    • 有点味道,因为需要完全复制 DefaultRolloverStrategy 的工厂方法(使此解决方案维护不友好,例如,如果 DefaultRolloverStrategy 在未来版本中获取更多配置参数)

    要让log4j2调用我们的工厂方法,log4j2.xml的根标签必须带有我们类的包,例如:

    <Configuration packages="de.jme.toolbox.logging">
    ...
    </Configuration>
    

    在我们自己的 RolloverStrategy 中,我们必须按照 here 的描述处理 @Plugin@PluginFactory

    最后是我完整的 log4j2.xml(您不需要所有这些属性 - 这正是我喜欢配置日志记录的方式):

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration packages="de.jme.toolbox.logging">
      <Properties>
        <Property name="projectPrefix">Tts</Property>
        <Property name="rawPattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</Property>
        <Property name="coloredPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] %C{2} (%F:%L) -}{bright,black} %m%n</Property>
        <Property name="coloredShortPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] -}{bright,black} %m%n</Property>
        <Property name="fileName">Log/${projectPrefix}.log</Property>
        <Property name="filePattern">Log/${projectPrefix}-%i.log</Property>
      </Properties>
      <Appenders>
        <Console name="Stdout" target="SYSTEM_OUT">
          <PatternLayout pattern="${coloredPattern}"/>
        </Console>
        <RollingFile name="Logfile" fileName="${fileName}" filePattern="${filePattern}">
          <PatternLayout pattern="${rawPattern}"/>
          <Policies>
            <SizeBasedTriggeringPolicy size="1 MB"/>
          </Policies>
          <MyRolloverStrategy fileIndex="min" max="16"/>
        </RollingFile>
      </Appenders>
      <Loggers>
        <Root level="info">
          <AppenderRef ref="Stdout"/>
          <AppenderRef ref="Logfile"/>
        </Root>
      </Loggers>
    </Configuration>
    

    这里是 MyRolloverStrategy.java:

    package de.jme.toolbox.logging;
    
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.zip.Deflater;
    
    import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
    import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
    import org.apache.logging.log4j.core.appender.rolling.RolloverDescription;
    import org.apache.logging.log4j.core.appender.rolling.helper.Action;
    import org.apache.logging.log4j.core.config.Configuration;
    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    import org.apache.logging.log4j.core.helpers.Integers;
    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.status.StatusLogger;
    
    /**
     * Own RolloverStrategy to hook the DefaultRolloverStrategy's rollover events
     *
     * Siehe auch:
     * - https://issues.apache.org/jira/browse/LOG4J2-486
     * - http://apache-logging.6191.n7.nabble.com/log4j2-getting-started-amp-rolling-files-tt8406.html#a42402
     * - http://stackoverflow.com/questions/20819376/log4j2-rollingfile-appender-add-custom-info-at-the-start-of-each-logfile
     *
     * @author Joe Merten
     */
    @org.apache.logging.log4j.core.config.plugins.Plugin(name="MyRolloverStrategy", category="Core", printObject=true)
    public class MyRolloverStrategy extends DefaultRolloverStrategy {
    
        protected static final Logger logger = StatusLogger.getLogger();
    
        // ==============================
        // ↓↓↓ Some stuff copied from ↓↓↓
        // https://svn.apache.org/repos/asf/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java r1556050
        // Just changed »DefaultRolloverStrategy« to »MyRolloverStrategy«
    
        private static final int MIN_WINDOW_SIZE = 1;
        private static final int DEFAULT_WINDOW_SIZE = 7;
    
        @PluginFactory
        public static MyRolloverStrategy createStrategy(
                @PluginAttribute("max") final String max,
                @PluginAttribute("min") final String min,
                @PluginAttribute("fileIndex") final String fileIndex,
                @PluginAttribute("compressionLevel") final String compressionLevelStr,
                @PluginConfiguration final Configuration config) {
            final boolean useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max");
            int minIndex;
            if (min != null) {
                minIndex = Integer.parseInt(min);
                if (minIndex < 1) {
                    LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE);
                    minIndex = MIN_WINDOW_SIZE;
                }
            } else {
                minIndex = MIN_WINDOW_SIZE;
            }
            int maxIndex;
            if (max != null) {
                maxIndex = Integer.parseInt(max);
                if (maxIndex < minIndex) {
                    maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
                    LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex);
                }
            } else {
                maxIndex = DEFAULT_WINDOW_SIZE;
            }
            final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
            return new MyRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, config.getStrSubstitutor());
        }
        // ↑↑↑ Some stuff copied from ↑↑↑
        // ==============================
    
        protected MyRolloverStrategy(int minIndex, int maxIndex, boolean useMax, int compressionLevel, StrSubstitutor subst) {
            super(minIndex, maxIndex, useMax, compressionLevel, subst);
        }
    
        // Wrapper class only for setting a hook to execute()
        static class MyAction implements Action {
            final Action delegate;
            final String fileName;
    
            public MyAction(final Action delegate, final String fileName) {
                this.delegate = delegate;
                this.fileName = fileName;
            }
    
            @Override public void run() {
                delegate.run();
            }
    
            @Override public boolean execute() throws IOException {
                try {
                    BufferedWriter writer = null;
                    try {
                        writer = new BufferedWriter(new FileWriter(new File(fileName), true));
                        writer.write("****************************\n");
                        writer.write("*** Bye, bye old logfile ***\n");
                        writer.write("****************************\n");
                    } finally {
                        if (writer != null)
                            writer.close();
                    }
                } catch (Throwable e) {
                    logger.error("Writing to bottom of old logfile \"" + fileName + "\" with", e);
                }
    
                boolean ret = delegate.execute();
    
                try {
                    BufferedWriter writer = null;
                    try {
                        writer = new BufferedWriter(new FileWriter(new File(fileName), true));
                        writer.write("*************************\n");
                        writer.write("*** Hello new logfile ***\n");
                        writer.write("*************************\n");
                    } finally {
                        if (writer != null)
                            writer.close();
                    }
                } catch (Throwable e) {
                    logger.error("Writing to top of new logfile \"" + fileName + "\" with", e);
                }
    
                return ret;
            }
    
            @Override public void close() {
                delegate.close();
            }
    
            @Override public boolean isComplete() {
                return delegate.isComplete();
            }
        }
    
        // Wrapper class only for setting a hook to getSynchronous().execute()
        static class MyRolloverDescription implements RolloverDescription {
            final RolloverDescription delegate;
    
            public MyRolloverDescription(final RolloverDescription delegate) {
                this.delegate = delegate;
            }
    
            @Override public String getActiveFileName() {
                return delegate.getActiveFileName();
            }
    
            @Override public boolean getAppend() {
                //return delegate.getAppend();
                // As long as we already put some data to the top of the new logfile, subsequent writes should be performed with "append".
                return true;
            }
    
            // The synchronous action is for renaming, here we want to hook
            @Override public Action getSynchronous() {
                Action delegateAction = delegate.getSynchronous();
                if (delegateAction == null) return null;
                return new MyAction(delegateAction, delegate.getActiveFileName());
            }
    
            // The asynchronous action is for compressing, we don't need to hook here
            @Override public Action getAsynchronous() {
                return delegate.getAsynchronous();
            }
        }
    
        public RolloverDescription rollover(final RollingFileManager manager) {
            RolloverDescription ret = super.rollover(manager);
            return new MyRolloverDescription(ret);
        }
    }
    

    如果我发布的feature request 能够实现,那么在 log4j2 的未来版本中解决这个要求可能会更容易。

    【讨论】:

      【解决方案2】:

      新的解决方案可用

      自从提出这个问题以来已经过去了一段时间,现在,当我想做同样的事情时,我发现可以在不弄乱工厂的情况下解决,但并不容易找到。 log4j2 团队通过配置实现了这一点。我希望我的帖子对其他人有用并节省时间。

      他们在 PatternLayout 元素中隐藏了这个功能。不是我第一次看到的地方,但为什么在它起作用时抱怨?

      这是我的配置(注意页眉和页脚,以及它们使用的属性):

      <?xml version="1.0" encoding="UTF-8"?>
      <Configuration>
          <Properties>
              <Property name="log-path">${sys:catalina.home}/logs/my-app</Property>
              <Property name="archive">${log-path}/archive</Property>
              <Property name="defaultPattern">[%d] [%-5p] [%t] %C{5} - %m%n</Property>
              <Property name="defaultRollOverMax">450</Property>
              <Property name="fileHeader">[%d] Start of log \n========================================================================\n
      Will be archived in ${archive}\n\n</Property>
              <Property name="fileFooter">\n========================================================================\n[%d] End of log</Property>
          </Properties>
          <Appenders>
              <Console name="Console" target="SYSTEM_OUT">
                  <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
                  <PatternLayout pattern="%highlight{[%d] [%-5p] [%t] %C{3} (%F:%L) - %m%n}" charset="UTF-8"/>
              </Console>
      
              <RollingFile name="Root"
                           fileName="${log-path}/root.log"
                           filePattern="${archive}/root.log.%d{yyyy-MM-dd}_%i.gz"
                           immediateFlush="true">
                  <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
                  <Policies>
                      <OnStartupTriggeringPolicy/>
                      <SizeBasedTriggeringPolicy size="10 MB"/>
                      <TimeBasedTriggeringPolicy/>
                  </Policies>
      
                  <PatternLayout pattern="${defaultPattern}"
                                 charset="UTF-8"
                                 header="This is the ROOT logger and it should be silent \n - define loggers when you see something in here"
                                 footer="Closing"/>
                  <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
              </RollingFile>
      
              <RollingFile name="System"
                           fileName="${log-path}/system.log"
                           filePattern="${archive}/system.log.%d{yyyy-MM-dd}_%i.gz"
                           immediateFlush="true">
                  <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
                  <Policies>
                      <OnStartupTriggeringPolicy/>
                      <SizeBasedTriggeringPolicy size="10 MB"/>
                      <TimeBasedTriggeringPolicy/>
                  </Policies>
                  <PatternLayout pattern="${defaultPattern}"
                                 charset="UTF-8"
                                 header="${fileHeader}"
                                 footer="${fileFooter}"/>
                  <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
              </RollingFile>
      
              <RollingFile name="Error"
                           fileName="${log-path}/error.log"
                           filePattern="${archive}/error.log.%d{yyyy-MM-dd}_%i.gz"
                           immediateFlush="true">
                  <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
                  <Policies>
                      <OnStartupTriggeringPolicy/>
                      <SizeBasedTriggeringPolicy size="10 MB"/>
                      <TimeBasedTriggeringPolicy/>
                  </Policies>
                  <PatternLayout pattern="${defaultPattern}"
                                 charset="UTF-8"
                                 header="${fileHeader}"
                                 footer="${fileFooter}"/>
                  <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
              </RollingFile>
          </Appenders>
      
          <Loggers>
              <Logger name="org.myOrganization.myApplication" additivity="false" level="INFO">
                  <appender-ref ref="System"/>
                  <appender-ref ref="Console"/>
                  <appender-ref ref="Error"/>
              </Logger>
      
              <Logger name="org.myOrganization.myApplication.peculiarPackage" additivity="false" level="TRACE">
                  <appender-ref ref="System"/>
                  <appender-ref ref="Console"/>
                  <appender-ref ref="Error"/>
              </Logger>
      
              <Logger name="org.springframework" additivity="false" level="WARN">
                  <appender-ref ref="System"/>
                  <appender-ref ref="Console"/>
                  <appender-ref ref="Error"/>
              </Logger>
      
              <Logger name="javax" additivity="false" level="WARN">
                  <appender-ref ref="System"/>
                  <appender-ref ref="Console"/>
                  <appender-ref ref="Error"/>
              </Logger>
      
              <!-- Root logger should be empty -->
              <Root level="all">
                  <AppenderRef ref="Root"/>
                  <AppenderRef ref="Console"/>
                  <!--Make sure all errors are logged to the error log-->
                  <appender-ref ref="Error"/>
              </Root>
          </Loggers>
      </Configuration>
      

      如您所见,我已经包含了一个时间戳和一个包含系统属性的属性。 Log4j2 可以显示许多不同类型的属性,通过它你可以做很多你要求的事情。

      日志文件如下所示:

      [2016-08-09 17:00:43,924] Start of log 
      ========================================================================
      
      Will be archived in /home/emanciperingsivraren/program/apache-tomcat-8.0.32/logs/my-app/archive
      
      [2016-08-09 17:00:44,000] [INFO ] [RMI TCP Connection(2)-127.0.0.1]    [snip]
      
      ========================================================================
      [2016-08-09 17:02:17,871] End of log
      

      您需要更多自定义信息吗? - 尝试将这些信息放在属性、系统属性或 log4j2 可以读取的其他内容中。

      请参阅Property Substitution in log4j2,了解您可以拥有哪些属性的详细信息。

      关于配置的评论

      • 我使用属性而不是重复常用设置
      • 如果在根记录器中写入了某些内容,那么它是未处理的,应该编写一个记录器来定义我们想要用它做什么
      • 由于错误/异常可以“隐藏”在许多其他日志消息中,因此它们也会打印在那里,并且很容易找到
      • 控制台上打印的消息有不同的颜色

      【讨论】:

        【解决方案3】:

        目前没有用于翻转的回调挂钩。 我可以建议在 log4j2 问题跟踪器中将此作为功能请求提出吗?

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-10
        • 1970-01-01
        相关资源
        最近更新 更多