In log4j2, how to configure renameEmptyFiles to be

2019-05-26 22:34发布

问题:

I'm using log4j 2 and RollingFile appender:

<RollingFile name="mylog"
fileName="mylog.log"
filePattern="mylog.log.%d{yyyy-MM-dd}.log">
  <PatternLayout>
    <pattern>[%d] [%-5p] [%-8t] %F:%L %m%n</pattern>
  </PatternLayout>
  <Policies>
    <TimeBasedTriggeringPolicy interval="1"/>
  </Policies>
</RollingFile>

The log files do get renamed daily. But the Javadoc of FileRenameAction class indicates there is an option renameEmptyFiles which is false by default so if a day's log is empty it deletes it instead of rename it appending the date to the file name. How to configure it to true since I'd like to have the log file even if it's empty?

回答1:

I made a little plugin that offers the desired functionality. I simply extended the DefaultRolloverStrategy and replaced (as all of its fields are final) the RolloverDescription object that is returned from rollover(). I copied the static @PluginFactory code from DefaultRolloverStrategy as it's required for the Log4j 2 plugin system.

Here's the code:

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.RolloverDescriptionImpl;
import org.apache.logging.log4j.core.appender.rolling.action.Action;
import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
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.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.util.Integers;

@Plugin( name = "KeepEmptyFilesRolloverStrategy", category = "Core", printObject = true )
public class KeepEmptyFilesRolloverStrategy extends DefaultRolloverStrategy
{
   private static final int MIN_WINDOW_SIZE = 1;
   private static final int DEFAULT_WINDOW_SIZE = 7;

   @PluginFactory
   public static KeepEmptyFilesRolloverStrategy createStrategy( @PluginAttribute( "max" ) final String max,
                                                                @PluginAttribute( "min" ) final String min,
                                                                @PluginAttribute( "fileIndex" ) final String fileIndex,
                                                                @PluginAttribute( "compressionLevel" ) final String compressionLevelStr,
                                                                @PluginElement( "Actions" ) final Action[] customActions,
                                                                @PluginAttribute( value = "stopCustomActionsOnError", defaultBoolean = true ) final boolean stopCustomActionsOnError,
                                                                @PluginConfiguration final Configuration config )
   {
      final boolean useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase( "max" );
      int minIndex = MIN_WINDOW_SIZE;
      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;
         }
      }
      int maxIndex = DEFAULT_WINDOW_SIZE;
      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 );
         }
      }
      final int compressionLevel = Integers.parseInt( compressionLevelStr, Deflater.DEFAULT_COMPRESSION );
      return new KeepEmptyFilesRolloverStrategy( minIndex,
                                                 maxIndex,
                                                 useMax,
                                                 compressionLevel,
                                                 config.getStrSubstitutor(),
                                                 customActions,
                                                 stopCustomActionsOnError );
   }

   protected KeepEmptyFilesRolloverStrategy( int minIndex,
                                             int maxIndex,
                                             boolean useMax,
                                             int compressionLevel,
                                             StrSubstitutor subst,
                                             Action[] customActions,
                                             boolean stopCustomActionsOnError )
   {
      super( minIndex, maxIndex, useMax, compressionLevel, subst, customActions, stopCustomActionsOnError );
   }

   @Override
   public RolloverDescription rollover( final RollingFileManager manager ) throws SecurityException
   {
      RolloverDescription oldResult = super.rollover( manager );

      // Fail fast (ClassCastException) if implementation of DefaultRolloverStrategy 
      // ever changes and uses a different Action type. 
      FileRenameAction oldRenameAction = (FileRenameAction) oldResult.getSynchronous();
      FileRenameAction newRenameAction = new FileRenameAction( oldRenameAction.getSource(),
                                                               oldRenameAction.getDestination(),
                                                               true );

      RolloverDescription newResult = new RolloverDescriptionImpl( oldResult.getActiveFileName(),
                                                                   oldResult.getAppend(),
                                                                   newRenameAction,
                                                                   oldResult.getAsynchronous() );

      return newResult;
   }
}

To use this class, simply reference it in the Log4j 2 XML configuration, e.g. like this:

<RollingFile name="RollingFile" fileName="/usr/local/glassfish4.1-webprofile/glassfish/domains/domain1/logs/server.log" filePattern="/usr/local/glassfish4.1-webprofile/glassfish/domains/domain1/logs/server.%d{yyyyMMdd-HH:mm}.log">
  <KeepEmptyFilesRolloverStrategy/>
  <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
  <CronTriggeringPolicy schedule="0 * * * * ?"/>
</RollingFile>

The implementation was inspired by this related answer.

On a sidenote, it might be necessary to use the new CronTriggeringPolicy to have empty log files being created at all, as it uses a separate thread. Judging from some other answers on SO, at least some of the other policies cannot react to the trigger as long as the Appender doesn't write out anything.