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 "Bohdan Mushkevych (JIRA)" <ji...@apache.org> on 2014/02/13 00:29:20 UTC

[jira] [Comment Edited] (LOG4J2-491) RollingFile Appender - callbacks when rolling

    [ 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/12/14 11:28 PM:
--------------------------------------------------------------------

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 


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>
                            <!-- %p stands for logging level -->
                            <!-- %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="500" />
                            <!--<SizeBasedTriggeringPolicy size="64 MB" />-->
                        </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-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org