[ 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