Wednesday, July 30, 2014

Log4j: Make a file appender that rolls the log file every time the application runs

When to Use It:

You want your log file that opens as a new file every time the application runs. The previous log file will be saved with a serial number appended to its file name.

For example,

stdout.log
stdout.log.1
stdout.log.2
...

How to Do It:

Let the code explain everything.

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Files;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.ErrorCode;

public class EveryRunRollingFileAppender extends FileAppender {

    public EveryRunRollingFileAppender() {
        super();
    }

    public EveryRunRollingFileAppender(Layout layout, String filename,
            boolean append, boolean bufferedIO, int bufferSize)
            throws IOException {
        super(layout, filename, append, bufferedIO, bufferSize);
    }

    public EveryRunRollingFileAppender(Layout layout, String filename, boolean append)
            throws IOException {
        super(layout, filename, append);
    }

    public EveryRunRollingFileAppender(Layout layout, String filename)
            throws IOException {
        super(layout, filename);
    }

    @Override
    public void activateOptions() {
        if (fileName != null) {
            try {
                rollLogFile();
                setFile(fileName, false, bufferedIO, bufferSize);
            } catch (IOException e) {
                errorHandler.error("setFile(" + fileName + "," + fileAppend
                        + ") call failed.", e, ErrorCode.FILE_OPEN_FAILURE);
            }
        } else {
            LogLog.warn("File option not set for appender [" + name + "].");
            LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
        }
    }

    private void rollLogFile() throws IOException {
        File logFile = new File(fileName);

        if (!logFile.exists()) {
            return;
        }

        String logFilenameSuffix = logFile.getName() + ".";

        File logDir = logFile.getParentFile();
        String[] logFilenames = logDir.list(new RollingFilenameFilter(logFilenameSuffix));

        long lastLogFileSeq = 0;

        for (String logFilename : logFilenames) {
            String logFileSeqName = logFilename.substring(
                    logFilename.lastIndexOf(logFilenameSuffix) + logFilenameSuffix.length());

            try {
                long logFileSeq = Long.valueOf(logFileSeqName);

                if (logFileSeq > lastLogFileSeq) {
                    lastLogFileSeq = logFileSeq;
                }
            } catch (NumberFormatException e) {
            }
        }

        StringBuilder rollingLogFilename = new StringBuilder(fileName);
        rollingLogFilename.append(".");
        rollingLogFilename.append(lastLogFileSeq + 1);

        File rollingLogFile = new File(rollingLogFilename.toString());

        Files.copy(logFile.toPath(), rollingLogFile.toPath());
    }

    private static class RollingFilenameFilter implements FilenameFilter {

        private final String logFilenameSuffix;

        RollingFilenameFilter(String logFilenameSuffix) {
            this.logFilenameSuffix = logFilenameSuffix;
        }

        @Override
        public boolean accept(File dir, String name) {
            return name.lastIndexOf(logFilenameSuffix) != -1;
        }
    }
}

How to Use It:

Set the custom appender in your log4j.properties.

log4j.appender.file=com.foo.logging.EveryRunRollingFileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%-5p %d{ISO8601} [%c.%M():%L] - %m%n
log4j.appender.file.file=stdout.log