[ 
https://issues.apache.org/jira/browse/LOG4J2-491?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13899741#comment-13899741
 ] 

Bohdan Mushkevych edited comment on LOG4J2-491 at 2/13/14 12:08 AM:
--------------------------------------------------------------------

Hi All!

I am building CSV logger based on StructuredDataMessage, dynamic routing and 
hourly file rotations. To address the CSV header issue, I am using slightly 
modified version of Joe's MyRolloverStrategy (BTW: Thank you Joe for publishing 
your research efforts):

{code:java}
@org.apache.logging.log4j.core.config.plugins.Plugin(name="LogStreamRolloverStrategy",
 category="Core", printObject=true)
public class LogStreamRolloverStrategy extends DefaultRolloverStrategy {

    protected static final Logger logger = StatusLogger.getLogger();
    protected String tableName;

    private static final int MIN_WINDOW_SIZE = 1;
    private static final int DEFAULT_WINDOW_SIZE = 7;

    @PluginFactory
    public static LogStreamRolloverStrategy createStrategy(
            @PluginAttribute("max") final String max,
            @PluginAttribute("min") final String min,
            @PluginAttribute("fileIndex") final String fileIndex,
            @PluginAttribute("compressionLevel") final String 
compressionLevelStr,
            @PluginAttribute("tableName") final String tableName,
            @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 LogStreamRolloverStrategy(minIndex, maxIndex, useMax, 
compressionLevel, tableName, config.getStrSubstitutor());
    }
    
    protected LogStreamRolloverStrategy(int minIndex,
                                        int maxIndex,
                                        boolean useMax,
                                        int compressionLevel,
                                        String tableName,
                                        StrSubstitutor subst) {
        super(minIndex, maxIndex, useMax, compressionLevel, subst);
        this.tableName = tableName;
    }

    // Wrapper class only for setting a hook to execute()
    static class LogStreamAction implements Action {
        final Action delegate;
        final String fileName;
        final String header;
        final String footer;

        public LogStreamAction(final Action delegate, final String fileName, 
final String header, final String footer) {
            this.delegate = delegate;
            this.fileName = fileName;
            this.header = header;
            this.footer = footer;
        }

        @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(footer + System.getProperty("line.separator"));
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to bottom of an 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(header + System.getProperty("line.separator"));
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to top of a 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 LogStreamRolloverDescription implements RolloverDescription {
        public static final String EMPTY_STRING = "";

        final RolloverDescription delegate;
        final String tableName;

        public LogStreamRolloverDescription(final RolloverDescription delegate, 
final String tableName) {
            this.delegate = delegate;
            this.tableName = tableName;
        }

        public String getCsvHeader() {
            StringBuilder sbCsvHeader = new StringBuilder();
            // compose the csv header 
            return sbCsvHeader.toString();
        }

        @Override
        public String getActiveFileName() {
            return delegate.getActiveFileName();
        }

        @Override
        public boolean getAppend() {
            // As soon as we have 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;
            }

            String footer = EMPTY_STRING;
            String header = getCsvHeader();

            return new LogStreamAction(delegateAction, 
delegate.getActiveFileName(), header, footer);
        }

        // 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 LogStreamRolloverDescription(ret, tableName);
    }
}
{code}

Corresponding initialization log4j2.xml file is as follows:
{code:xml}
        <Routing name="Routing">
            <Routes pattern="$${sd:type}">
                <Route>
                    <RollingFile name="RollingFile-${sd:type}"
                                 
fileName="logs/${date:yyyyMMdd}/${date:yyyyMMddHH}-${sd:type}-${hostName}.log"
                                 
filePattern="logs/${date:yyyyMMdd}/%d{yyyyMMddHH}-${sd:type}-${hostName}.%i.log.gz">
                        <PatternLayout>
                            <!-- %K{m} stands for the message passed in 
StructuredDataMessage map with key "m" -->
                            <!-- %n stands for new line -->
                            <pattern>%K{m}%n</pattern>
                        </PatternLayout>
                        <Policies>
                            <SizeBasedTriggeringPolicy size="1024" />
                        </Policies>
                        <LogStreamRolloverStrategy tableName="${sd:type}" 
max="999"/>
                    </RollingFile>
                </Route>
            </Routes>
        </Routing>
{code}

Above code works well after the .log file roll-over. However, the very first 
.log file contains no header.
Please, advice what should I override/implement to insert a header into a very 
first .log file?

Dan 


was (Author: mushkevych):
Hi All!

I am using slightly modified version of Joe's MyRolloverStrategy (BTW: Thank 
you Joe for publishing your research efforts):

{code:java}
@org.apache.logging.log4j.core.config.plugins.Plugin(name="LogStreamRolloverStrategy",
 category="Core", printObject=true)
public class LogStreamRolloverStrategy extends DefaultRolloverStrategy {

    protected static final Logger logger = StatusLogger.getLogger();
    protected String tableName;

    private static final int MIN_WINDOW_SIZE = 1;
    private static final int DEFAULT_WINDOW_SIZE = 7;

    @PluginFactory
    public static LogStreamRolloverStrategy createStrategy(
            @PluginAttribute("max") final String max,
            @PluginAttribute("min") final String min,
            @PluginAttribute("fileIndex") final String fileIndex,
            @PluginAttribute("compressionLevel") final String 
compressionLevelStr,
            @PluginAttribute("tableName") final String tableName,
            @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 LogStreamRolloverStrategy(minIndex, maxIndex, useMax, 
compressionLevel, tableName, config.getStrSubstitutor());
    }
    
    protected LogStreamRolloverStrategy(int minIndex,
                                        int maxIndex,
                                        boolean useMax,
                                        int compressionLevel,
                                        String tableName,
                                        StrSubstitutor subst) {
        super(minIndex, maxIndex, useMax, compressionLevel, subst);
        this.tableName = tableName;
    }

    // Wrapper class only for setting a hook to execute()
    static class LogStreamAction implements Action {
        final Action delegate;
        final String fileName;
        final String header;
        final String footer;

        public LogStreamAction(final Action delegate, final String fileName, 
final String header, final String footer) {
            this.delegate = delegate;
            this.fileName = fileName;
            this.header = header;
            this.footer = footer;
        }

        @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(footer + System.getProperty("line.separator"));
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to bottom of an 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(header + System.getProperty("line.separator"));
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to top of a 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 LogStreamRolloverDescription implements RolloverDescription {
        public static final String EMPTY_STRING = "";

        final RolloverDescription delegate;
        final String tableName;

        public LogStreamRolloverDescription(final RolloverDescription delegate, 
final String tableName) {
            this.delegate = delegate;
            this.tableName = tableName;
        }

        public String getCsvHeader() {
            StringBuilder sbCsvHeader = new StringBuilder();
            // compose the csv header 
            return sbCsvHeader.toString();
        }

        @Override
        public String getActiveFileName() {
            return delegate.getActiveFileName();
        }

        @Override
        public boolean getAppend() {
            // As soon as we have 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;
            }

            String footer = EMPTY_STRING;
            String header = getCsvHeader();

            return new LogStreamAction(delegateAction, 
delegate.getActiveFileName(), header, footer);
        }

        // 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 LogStreamRolloverDescription(ret, tableName);
    }
}
{code}

Corresponding initialization log4j2.xml file is as follows:
{code:xml}
        <Routing name="Routing">
            <Routes pattern="$${sd:type}">
                <Route>
                    <RollingFile name="RollingFile-${sd:type}"
                                 
fileName="logs/${date:yyyyMMdd}/${date:yyyyMMddHH}-${sd:type}-${hostName}.log"
                                 
filePattern="logs/${date:yyyyMMdd}/%d{yyyyMMddHH}-${sd:type}-${hostName}.%i.log.gz">
                        <PatternLayout>
                            <!-- %K{m} stands for the message passed in 
StructuredDataMessage map with key "m" -->
                            <!-- %n stands for new line -->
                            <pattern>%K{m}%n</pattern>
                        </PatternLayout>
                        <Policies>
                            <SizeBasedTriggeringPolicy size="1024" />
                        </Policies>
                        <LogStreamRolloverStrategy tableName="${sd:type}" 
max="999"/>
                    </RollingFile>
                </Route>
            </Routes>
        </Routing>
{code}

Above code works well after the .log file roll-over. However, the very first 
.log file contains no header.
What should I override/implement a hook to insert a header into a very first 
.log file.

Dan 

> RollingFile Appender - callbacks  when rolling
> ----------------------------------------------
>
>                 Key: LOG4J2-491
>                 URL: https://issues.apache.org/jira/browse/LOG4J2-491
>             Project: Log4j 2
>          Issue Type: Wish
>          Components: Appenders
>    Affects Versions: 2.0-beta9
>         Environment: Java 1.7, Linux
>            Reporter: Joe Merten
>
> I want this callbacks to add some custom info at the top of each logfile, 
> like the version string of my application, the application uptime and the 
> system uptime. And even writing some »bye, bye / eof« to the bottom of the 
> just closed logfile would also be fine.
> See also:
> * [LOG4J2-486]
> * 
> [http://stackoverflow.com/questions/20819376/log4j2-rollingfile-appender-add-custom-info-at-the-start-of-each-logfile]
> Currently I need to extend DefaultRolloverStrategy and wrap around 
> RolloverDescription and appender.rolling.helper.Action to place my code
> (and therefore I also have to copy some factory code of 
> DefaultRolloverStrategy to support all config parameters etc.). Ok, this 
> approach currently works but it needs ~150 lines of code and is 
> maintenance-unfriendly eg. if DefaultRolloverStrategy gets more config 
> parameters in future versions.
> The callbacks should provide at least the filename of the related logfile.
> An access to the configured Layout of the related appender would also 
> helpful, so that the custom info could be correctly formatted (e.g. incl. 
> timestamp or matching for xml etc.).
> I think there sould be 2 callbacks
> * 1st one for after all outstanding writes to the old logfile has been done 
> (so thet I could add some stuff to the end of the old file), but before 
> things like compressing are performed
> * 2nd one for to write my stuff to the top of the new logfile
> Ok, there must be take care of Layout.getHeader/Footer() (which is used by 
> e.g. XMLLayout) considering if the callbacks should be called e.g. "inside or 
> outside of the xml root tags".
> If I want to add my custom info as a kind of LogEvents, then the callbacks 
> should be called before writing the Layout.getFooter() and after writing 
> Layout.getHeader() to the file.
> But if someone want to add custom info outside of the xml root tags (e.g. 
> someone might calculate a checksum over the logfile and write that to the 
> very last line of the file) then the callbacks must be called outside 
> header/footer.
> For my current needs, I require the callbacks inside of header/footer 
> (although it not really matters for me while I currently only using 
> PatternLayout).
> Bug finally that looks that we need callbacks for both cases.



--
This message was sent by Atlassian JIRA
(v6.1.5#6160)

---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscr...@logging.apache.org
For additional commands, e-mail: log4j-dev-h...@logging.apache.org

Reply via email to