You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by "Samuel D. Franklin" <sf...@crowechizek.com> on 2002/11/12 23:15:23 UTC

Compressed file recognition for RollingFileAppender

I was using the RollingFileAppender to keep a fairly large number of log
files for an application, and I started to run out of disk space. So I
compressed a bunch of the old files, but then I lost the appender's ability
to rotate out the oldest files. So I gave the appender the ability to
recognize compressed file extensions. I thought the rest of the world might
be able to use this, too, so I figured I'd share. Here's the subclass I
created:

package org.apache.log4j;

import java.io.*;
import java.util.Vector;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.RollingFileAppender;
import org.apache.log4j.helpers.CountingQuietWriter;

/**
 * Allows RollingFileAppender to recognize compressed versions of old
files.
 *
 * The default list of compression format extensions is {"", ".gz"}. This
may
 * be changed by specifying (using the format .gz,.zip,.Z,.bz2) the
 * CompressionFormats property for this appender in a config file. Note
that
 * the blank extension will be added when the property is read, so is not
 * necessary in the config file. Note also that keeping this list short is
 * recommended for performance reasons, especially if you also are keeping
 * large numbers of backup files.
 *
 * @author Skip Franklin
 */
public class RollingFileAppenderWithCompression extends RollingFileAppender
{
    /**
     * The filename extensions of the compression formats supported.
     */
    protected String[] compressionFormatList = {"", ".gz"};

    /**
     The default constructor simply calls its {@link
     FileAppender#FileAppender parents constructor}.  */
    public RollingFileAppenderWithCompression()
    {
        super();
    }

    /**
    Instantiate an object and open the file designated by
    <code>filename</code>. The opened filename will become the ouput
    destination for this appender.

    <p>If the <code>append</code> parameter is true, the file will be
    appended to. Otherwise, the file desginated by
    <code>filename</code> will be truncated before being opened.
    */
    public RollingFileAppenderWithCompression(
        Layout layout, String filename, boolean append)
        throws IOException
    {
        super(layout, filename, append);
    }

    /**
     Instantiate a FileAppender and open the file designated by
    <code>filename</code>. The opened filename will become the output
    destination for this appender.

    <p>The file will be appended to.  */
    public RollingFileAppenderWithCompression(
        Layout layout, String filename) throws IOException
    {
        super(layout, filename);
    }

    /**
     Implements the usual roll over behaviour.

     <p>If <code>MaxBackupIndex</code> is positive, then files
     {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>}
     are renamed to {<code>File.2</code>, ...,
     <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is
     renamed <code>File.1</code> and closed. A new <code>File</code> is
     created to receive further log output.

     <p>If <code>MaxBackupIndex</code> is equal to zero, then the
     <code>File</code> is truncated with no backup files created.

     Note that this method is not synchronized since doAppend already is.
    */
    public void rollOver()
    {
        File target;
        File file;

        LogLog.debug("rolling over count=" + ((CountingQuietWriter)
qw).getCount());
        LogLog.debug("maxBackupIndex="+maxBackupIndex);

        // If maxBackups <= 0, then there is no file renaming to be done.
        if(maxBackupIndex > 0)
        {
            // Delete the oldest file, to keep Windows happy.
            file = new File(fileName + '.' + maxBackupIndex);
            if (file.exists())
            {
                file.delete();
            }

            // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex,
..., 3, 2}
            for (int i = maxBackupIndex - 1; i >= 1; i--)
            {
                compressedFileLoop:
                for (int j = 0; j < compressionFormatList.length; j++)
                {
                    file = new File(fileName + "." + i +
compressionFormatList[j]);
                    if (file.exists())
                    {
                        target = new File(
                            fileName + '.' + (i + 1) +
compressionFormatList[j]);
                        LogLog.debug("Renaming file " + file + " to " +
target);
                        file.renameTo(target);
                        break compressedFileLoop;
                    }
                }
            }

            // Rename fileName to fileName.1
            target = new File(fileName + "." + 1);

            this.closeFile(); // keep windows happy.

            file = new File(fileName);
            LogLog.debug("Renaming file " + file + " to " + target);
            file.renameTo(target);
        }

        try
        {
            // This will also close the file. This is OK since multiple
            // close operations are safe.
            this.setFile(fileName, false, bufferedIO, bufferSize);
        }
        catch(IOException e)
        {
            LogLog.error("setFile("+fileName+", false) call failed.", e);
        }
    }

    /** Get access method for list of compression formats. */
    public String[] getCompressionFormatList()
    {
        return compressionFormatList;
    }

    /** Set access method for list of compression formats. */
    public void setCompressionFormatList(String[] newList)
    {
        compressionFormatList = newList;
    }

    /** Sets the list of compression formats from a config string. */
    public void setCompressionFormats(String inputString)
    {
        String configString = new String(inputString);
        try
        {
            LogLog.debug("setting compression formats to: " +
configString);
            String[] nothing = {""};
            if (configString == null || configString.trim().equals(""))
            {
                setCompressionFormatList(nothing);
                return;
            }
            int commaLoc = configString.indexOf(',');
            Vector stuff = new Vector();
            stuff.add("");
            while (commaLoc != -1)
            {
                stuff.add(configString.substring(0, commaLoc));
                configString = configString.substring(commaLoc+1).trim();
                commaLoc = configString.indexOf(',');
            }
            if (!configString.equals(""))
            {
                stuff.add(configString);
            }
            setCompressionFormatList((String[])stuff.toArray(nothing));
        }
        catch (Exception e)
        {
            LogLog.error(
                "Error setting compression formats (" + inputString + "). "
+
                "Error was:" + e);
        }
    }
}


I considered putting the ability to compress the file during the rollover
in here as well, but decided against it for my application because of the
possible performance impact. I've got a separate process to do the actual
compression that runs during off-peak times. Any comments are appreciated,
and anyone is welcome to re-use this if the spirit moves them.

Skip Franklin
sfranklin@crowechizek.com




--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>