How to configure PeriodicSizeRotatingFileHandler in JBoss 7?

Depending on your version of JBoss AS 7 there is a org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler you can use as a custom-handler. I believe it was introduced in jboss-logmanager 1.3.0.Final. I can't recall which version of JBoss AS/WildFly it is in though.

Here's an example CLI command.

/subsystem=logging/custom-handler=example:add(class=org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler, module=org.jboss.logmanager, formatter="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n", properties={rotateSize=1024000,maxBackupIndex=20,suffix=".yyyy-MM-dd",fileName="${jboss.server.log.dir}/example.log"})

Here's what worked for me (make sure to change the rotationSize and maxBackupIndex to whatever makes sense for you. The numbers I have in there are just for testing):

        <custom-handler name="FILESIZEDATE" class="org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler" module="org.jboss.logmanager">
            <formatter>
                <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
            </formatter>
            <properties>
                <property name="autoFlush" value="true"/>
                <property name="append" value="true"/>
                <property name="rotateSize" value="1000"/>
                <property name="maxBackupIndex" value="20"/>
                <property name="suffix" value=".yyyy-MM-dd"/>
                <property name="fileName" value="${jboss.server.log.dir}/server.log"/>
            </properties>
        </custom-handler>

        <root-logger>
            <level name="INFO"/>
            <handlers>
                <handler name="FILESIZEDATE"/>
                <handler name="CONSOLE"/>
            </handlers>
        </root-logger>

Jboss 7 (AS) has support to either periodic-rotating-file-handler or size-rotating-file-handler but not both.

I get some reference(JBoss 7 Custom File Handler) to support periodic and size rotating. It is writing the Custom File Handler. JBoss 7 Custom File Handler is used to create daily rotating size limited log.

But, I update the source some part of original PeriodicSizeHandler.java for my requirement.

My updated source :

PeriodicSizeHandler.java

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.ErrorManager;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

public class PeriodicSizeHandler extends Handler {
    private int count = 1;
    protected static Long calculatedBytes;
    private String maxBytes = "2g";
    private String logFileName = "server.log";
    private static String logDirPath;
    private File logFile;
    private FileOutputStream outputStream;

    static {
        logDirPath = System.getenv("JBOSS_HOME") + "\\standalone\\log";
    }

    public enum Bytes {
        Byte("b", 1l), KiloBytes("k", (Byte.bytes * 1024)), MegaBytes("m",
                (KiloBytes.bytes * 1024)), GigaBytes("g",
                (MegaBytes.bytes * 1024)), ;

        private Bytes(String byteAcronym, Long bytes) {
            this.byteAcronym = byteAcronym;
            this.bytes = bytes;
        }

        private String byteAcronym;
        private Long bytes;

        public static long getBytes(String maxBytesRep) throws Exception {
            if (maxBytesRep == null && "".equals(maxBytesRep)) {
                throw new Exception(
                        "The max bytes representation cannot be empty or null");
            }
            String uByteRepresentation = maxBytesRep.toLowerCase();
            for (Bytes b : values()) {
                if (uByteRepresentation.endsWith(b.byteAcronym)) {
                    String strNumVal = uByteRepresentation.substring(0,
                            uByteRepresentation.indexOf(b.byteAcronym));
                    try {
                        return getBytes(Double.valueOf(strNumVal), b);
                    } catch (Exception e) {
                        throw new Exception(
                                "The max bytes representation: "
                                        + maxBytesRep
                                        + ", is not valid. Shoubl be of the form XXX..(B or b) / M or m /G or g). Ex: '1000b', '100m', '1g'");
                    }
                }
            }
            // If no acronym is mentioned consider it as Byte representation Ex.
            // maxBytes = 1000
            try {
                return getBytes(Double.valueOf(uByteRepresentation), Bytes.Byte);
            } catch (Exception e) {
                throw new Exception(
                        "The max bytes representation: "
                                + maxBytesRep
                                + ", is not valid. Shoubl      be of the form XXX../(B or b) / M or m /G or g). Ex: '1000', '1000b', '100m', '1g'");
            }
        }

        public String getByteAcronym() {
            return this.byteAcronym;
        }

        public Long getBytes() {
            return this.bytes;
        }

        public static long getBytes(double multiple, Bytes bytes) {
            return Math.round(multiple * bytes.bytes);
        }
    }

    public PeriodicSizeHandler() {
    }

    @Override
    public void flush() {
        try {
            if (outputStream != null) {
                outputStream.flush();
            }
        } catch (IOException e) {
            reportError(e.getMessage(), e, ErrorManager.FLUSH_FAILURE);
        }
    }

    @Override
    public void close() {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                reportError(e.getMessage(), e, ErrorManager.CLOSE_FAILURE);
            }

        }
    }

    @Override
    public void publish(LogRecord record) {
        init();
        if (rollOver(record)) {
            archiveFile();
            createLogChannel();
        }
        logToFile(record);
    }

    // Initialize
    public synchronized void init() {
        if (outputStream == null) {
            try {
                logFile = new File(getFilePath());
                if (logFile.exists()) {
                    outputStream = new FileOutputStream(logFile, true);
                } else {
                    createLogChannel();
                }
            } catch (FileNotFoundException e) {
                reportError(e.getMessage(), e, ErrorManager.OPEN_FAILURE);
            }
        }
    }

    private String getFilePath() {
        return getLogDirPath() + File.separator + logFileName;
    }

    // check the file size
    private boolean rollOver(LogRecord record) {
        try {
            StringBuilder logMessage = new StringBuilder(getFormatter().format(record));
            if ((outputStream.getChannel().size() + logMessage.length()) > getByteValue(getMaxBytes())) {
                return true;
            }
        } catch (IOException e) {
            reportError(e.getMessage(), e, ErrorManager.GENERIC_FAILURE);
        }
        return false;
    }

    /**
     * Rename with date and time stamp
     */
    private void archiveFile() {
        System.out.println("archiveFile..........");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
//      String logDir = getLogDirPath() +  File.separator + simpleDateFormat.format(new Date());
        String newFilePath = getLogDirPath()  + File.separator + logFileName + "_" + simpleDateFormat.format(new Date()) + "_" + count++;
        File newFile = new File(newFilePath);
        try {
            OutputStream oos = new FileOutputStream(newFile);
            byte[] buf = new byte[8192];
            InputStream is = new FileInputStream(logFile);
            int c = 0;
            while ((c = is.read(buf, 0, buf.length)) > 0) {
                oos.write(buf, 0, c);
                oos.flush();
            }
            oos.close();
            is.close();
            PrintWriter writer = new PrintWriter(logFile);
            writer.print("");
            writer.close();         
        } catch(Exception e) {
            reportError("Unable to rename old file: " + logFile.getName()
                    + " to new file: " + newFile, null, ErrorManager.GENERIC_FAILURE);
        }
    }

    private void createLogChannel() {
        try {
            // make directories
            File logDir = new File(getLogDirPath());
            logDir.mkdirs();

            // create log file
            System.out.println("getFilePath( : " + getFilePath());
            logFile = new File(getFilePath());
            logFile.createNewFile();

            // create the channel
            outputStream = new FileOutputStream(logFile, true);
        } catch (FileNotFoundException e) {
            reportError(e.getMessage(), e, ErrorManager.OPEN_FAILURE);
        } catch (IOException e) {
            reportError(e.getMessage(), e, ErrorManager.OPEN_FAILURE);
        }
    }

    private synchronized void logToFile(LogRecord record) {
        StringBuilder logMessage = new StringBuilder(getFormatter().format(record));
        try {
            if (outputStream != null) {
                outputStream.write(logMessage.toString().getBytes());
                outputStream.flush();
            }
        } catch (IOException e) {
            reportError(e.getMessage(), e, ErrorManager.WRITE_FAILURE);
        }
    }

    protected long getByteValue(String maxBytes) {
        if (calculatedBytes != null) {
            return calculatedBytes;
        }
        try {
            calculatedBytes = Bytes.getBytes(maxBytes);
        } catch (Exception e) {
            logToFile(new LogRecord(Level.INFO, "Failed to get byte value from maxBytes: " + maxBytes + ", exception: " + e));
            calculatedBytes = getDefaultBytes();
        }
        return calculatedBytes;
    }

    // Use Default - 2GB
    protected Long getDefaultBytes() {
        int multiple = 2;
        logToFile(new LogRecord(Level.INFO, "Using the default: '" + multiple + Bytes.GigaBytes.byteAcronym + "'"));
        return Bytes.getBytes(multiple, Bytes.GigaBytes);
    }

    public String getMaxBytes() {
        return maxBytes;
    }

    public void setMaxBytes(String maxBytes) {
        this.maxBytes = maxBytes;
    }

    public String getLogFileName() {
        return logFileName;
    }

    public void setLogFileName(String logFileName) {
        this.logFileName = logFileName;
    }

    public File getLogFile() {
        return logFile;
    }

    public void setLogFile(File logFile) {
        this.logFile = logFile;
    }

    public FileOutputStream getOutputStream() {
        return outputStream;
    }

    public void setOutputStream(FileOutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public void setLogDirPath(String logDirPath) {
        this.logDirPath = logDirPath;
    }

    public String getLogDirPath() {
        return logDirPath;
    }
}

PeriodicSizeHandlerTest.java

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import junit.framework.TestCase;

import org.junit.Before;
import org.junit.Test;

import com.cyc.jboss.logging.PeriodicSizeHandler.Bytes;

public class PeriodicSizeHandlerTest extends TestCase {
    @Before
    public void setUp() {
        PeriodicSizeHandler.calculatedBytes = null;
    }

    Long bytes = Bytes.getBytes(100, Bytes.Byte);

    @Test
    public void testPublishRollOverTrue() throws Exception {
        PeriodicSizeHandler psh = new PeriodicSizeHandler();
        psh.setMaxBytes("3k");
        setupFormatter(psh);
        // text.txt file size is `151k`
        FileReader fr = new FileReader("D:/temp/test.txt");
        BufferedReader br = new BufferedReader(fr);
        String message;
        while ((message = br.readLine()) != null) {
            LogRecord logRecord = new LogRecord(Level.INFO, message);
            psh.publish(logRecord);
        }
        fr.close();
        assertTrue(psh.getOutputStream() != null);
    }

    private void setupFormatter(PeriodicSizeHandler psh) {
        Formatter formatr = new Formatter() {
            @Override
            public String format(LogRecord record) {
                return record.getLevel() + " :: " + record.getMessage();
            }
        };
        psh.setFormatter(formatr);
    }

    public static void main(String[] args) {
        org.junit.runner.JUnitCore
                .main(PeriodicSizeHandlerTest.class.getName());
    }
}

I got my expected output file as below

server.log_2014-01-13_1
server.log_2014-01-13_2
server.log_2014-01-13_3
....
server.log_2014-01-13_55

If you would like use your customize file format, you just need to update archiveFile() method, I think so.

Next solution : after James R. Perkins suggestion.

Download jboss-logmanager-1.5.1.Final.jar here.

Replace old jboss-logmanger-xxxx.jar with jboss-logmanager-1.5.1.Final.jar at <JBOSS_HOME>\modules\org\jboss\logmanager\main directory.

Change your module.xml configuration as below

<module xmlns="urn:jboss:module:1.1" name="org.jboss.logmanager">
    <resources>
        <resource-root path="jboss-logmanager-1.5.1.Final.jar"/>
        <!-- Insert resources here -->
    </resources>

    <dependencies/>
</module>

Configuration for standalone.xml

<subsystem xmlns="urn:jboss:domain:logging:1.1">
    .....
    <custom-handler name="FILE" class="org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler" module="org.jboss.logmanager">
        <formatter>
            <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
        </formatter>
        <properties>
            <property name="file" value="D:\temp\server.log"/>  <--- your log directory          
            <property name="rotateSize" value="50000"/>         <--- 50000 byte
            <property name="maxBackupIndex" value="50"/>        
            <property name="append " value="true"/>
            <property name="suffix" value=".yyyy-MM-dd"/>
            <property name="autoflush" value="true"/>
        </properties>
    </custom-handler>
    ....
    <root-logger>
        <level name="INFO"/>
        <handlers>
            <handler name="CONSOLE"/>
            <handler name="FILE"/>
        </handlers>
    </root-logger>
</subsystem>    

Output :

server.log.2014-01-16.1 
server.log.2014-01-16.2 
server.log.2014-01-16.3