Configure interval time and print logs regularly
After receiving a request, the log is printed regularly through log4j. The requirement description is as follows: the log needs to be able to be printed regularly, and the time interval can be matched. Speaking of timing, first of all, I think of the DailyRollingFileAppender class. Various timings. According to datePattern, you can refer to the SimpleDateFormat class. Some common timing settings are as follows:
Through observation, I found that there is no date format similar to n minutes, so I wrote a custom class based on the DailyRollingFileAppender class. The process is as follows:
1) Copy the source code of the DailyRollingFileAppender class and rename it MinuteRollingAppender. In order to configure it in log4j.xml, add the configuration item intervalTime and add the set and get methods;
private int intervalTime = 10;
2) Since the DailyRollingFileAppender class uses the RollingCalendar class to calculate the next interval time, and needs to pass the parameter intervalTime, the RollingCalendar class is modified as an internal class; since its method is to calculate the time of the next rollOver action based on datePattern, no other time mode is needed at this time, the modification method is as follows:
public Date getNextCheckDate(Date now) { this.setTime(now); this.set(Calendar.SECOND, 0); this.set(Calendar.MILLISECOND, 0); this.add(Calendar.MINUTE, intervalTime); return getTime(); }3) When the time mode is matched according to the minutes, the time mode needs to be disabled. Change it to static final, and remove the datePattern parameters in the get, set method and MinuteRollingAppender constructor in the response.
private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";
Similarly, computeCheckPeriod(), which serves multiple datePatterns, can also be deleted; the transformation has been completed, and the finished product category is as follows:
package net.csdn.blog; import java.io.File; import java.io.IOException; import java.io.InterruptedIOException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import org.apache.log4j.FileAppender; import org.apache.log4j.Layout; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.spi.LoggingEvent; /** * Timerable by minute appender * * @author coder_xia * */ public class MinuteRollingAppender extends FileAppender { /** * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" * meaning daily rollover. */ private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; /** * Interval time, unit: minutes*/ private int intervalTime = 10; /** * The log file will be renamed to the value of the scheduledFilename * variable when the next interval is entered. For example, if the rollover * period is one hour, the log file will be renamed to the value of * "scheduledFilename" at the beginning of the next hour. * * The precision time when a rollover occurs depending on logging activity. */ private String scheduledFilename; /** * The next time we estimate a rollover should occur. */ private long nextCheck = System.currentTimeMillis() - 1; Date now = new Date(); SimpleDateFormat sdf; RollingCalendar rc = new RollingCalendar(); /** * The default constructor does nothing. */ public MinuteRollingAppender() { } /** * Instantiate a <code>MinuteRollingAppender</code> and open the file * designed by <code>filename</code>. The opened filename will become the * ouput destination for this appender. */ public MinuteRollingAppender(Layout layout, String filename) throws IOException { super(layout, filename, true); activateOptions(); } /** * @return the intervalTime */ public int getIntervalTime() { return intervalTime; } /** * @param intervalTime * the intervalTime to set */ public void setIntervalTime(int intervalTime) { this.intervalTime = intervalTime; } @Override public void activateOptions() { super.activateOptions(); if (fileName != null) { now.setTime(System.currentTimeMillis()); sdf = new SimpleDateFormat(DATEPATTERN); File file = new File(fileName); scheduledFilename = fileName + sdf.format(new Date(file.lastModified())); } else { LogLog .error("Either File or DatePattern options are not set for appender [" + name + "]."); } } /** * Rollover the current file to a new file. */ void rollOver() throws IOException { String datedFilename = fileName + sdf.format(now); // It is too early to roll over because we are still within the // bounds of the current interval. Rollover will occur once the // next interval is reached. if (scheduledFilename.equals(datedFilename)) { return; } // close current file, and rename it to datedFilename this.closeFile(); File target = new File(scheduledFilename); if (target.exists()) { target.delete(); } File file = new File(fileName); boolean result = file.renameTo(target); if (result) { LogLog.debug(fileName + " -> " + scheduledFilename); } else { LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "]."); } try { // This will also close the file. This is OK since multiple // close operations are safe. this.setFile(fileName, true, this.bufferedIO, this.bufferSize); } catch (IOException e) { errorHandler.error("setFile(" + fileName + ", true) call failed."); } scheduledFilename = datedFilename; } /** * This method differentiates MinuteRollingAppender from its super class. * * <p> * Before actually logging, this method will check whether it is time to do * a rollover. If it is, it will schedule the next rollover time and then * rollover. * */ @Override protected void subAppend(LoggingEvent event) { long n = System.currentTimeMillis(); if (n >= nextCheck) { now.setTime(n); nextCheck = rc.getNextCheckMillis(now); try { rollOver(); } catch (IOException ioe) { if (ioe instanceof InterruptedIOException) { Thread.currentThread().interrupt(); } LogLog.error("rollOver() failed.", ioe); } } super.subAppend(event); } /** * RollingCalendar is a helper class to MinuteRollingAppender. Given a * periodicity type and the current time, it computes the start of the next * interval. * */ class RollingCalendar extends GregorianCalendar { private static final long serialVersionUID = -3560331770601814177L; RollingCalendar() { super(); } public long getNextCheckMillis(Date now) { return getNextCheckDate(now).getTime(); } public Date getNextCheckDate(Date now) { this.setTime(now); this.set(Calendar.SECOND, 0); this.set(Calendar.MILLISECOND, 0); this.add(Calendar.MINUTE, intervalTime); return getTime(); } } }The test configuration file is as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="myFile"> <param name="File" value="log4jTest.log" /> <param name="Append" value="true" /> <param name="intervalTime" value="2"/> <layout> <param name="ConversionPattern" value="%p %d (%c:%L)- %m%n" /> </layout> </appender> <root> <priority value="debug"/> <appender-ref ref="myFile"/> </root> </log4j:configuration>
Regarding the timing implementation, you can also use the Timer implementation provided by Java, which eliminates the calculation and comparison of time every time you record the log. The difference is actually to set up a thread and call the rollOver method. The implementation is as follows:
package net.csdn.blog; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.apache.log4j.FileAppender; import org.apache.log4j.Layout; import org.apache.log4j.helpers.LogLog; public class TimerTaskRollingAppender extends FileAppender { /** * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" * meaning daily rollover. */ private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; /** * Interval time, unit: minutes*/ private int intervalTime = 10; SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN); /** * The default constructor does nothing. */ public TimerTaskRollingAppender() { } /** * Instantiate a <code>TimerTaskRollingAppender</code> and open the file * designed by <code>filename</code>. The opened filename will become the * ouput destination for this appender. */ public TimerTaskRollingAppender(Layout layout, String filename) throws IOException { super(layout, filename, true); activateOptions(); } /** * @return the intervalTime */ public int getIntervalTime() { return intervalTime; } /** * @param intervalTime * the intervalTime to set */ public void setIntervalTime(int intervalTime) { this.intervalTime = intervalTime; } @Override public void activateOptions() { super.activateOptions(); Timer timer = new Timer(); timer.schedule(new LogTimerTask(), 1000, intervalTime * 60000); } class LogTimerTask extends TimerTask { @Override public void run() { String datedFilename = fileName + sdf.format(new Date()); closeFile(); File target = new File(datedFilename); if (target.exists()) target.delete(); File file = new File(fileName); boolean result = file.renameTo(target); if (result) LogLog.debug(fileName + " -> " + datedFilename); else LogLog.error("Failed to rename [" + fileName + "] to [" + datedFilename + "]."); try { setFile(fileName, true, bufferedIO, bufferSize); } catch (IOException e) { errorHandler.error("setFile(" + fileName + ", true) call failed."); } } } } }However, there are two problems with the above implementation:
1) Concurrency
A place where concurrency problems may occur after calling closeFile() in run(), the subAppend() method just happens to write the log. At this moment, the file is closed, and the following error will be reported:
java.io.IOException: Stream closed at sun.nio.cs.StreamEncoder.ensureOpen(Unknown Source) at sun.nio.cs.StreamEncoder.write(Unknown Source) at sun.nio.cs.StreamEncoder.write(Unknown Source) at java.io.OutputStreamWriter.write(Unknown Source) at java.io.Writer.write(Unknown Source) ..................The solution is relatively simple. Just make the entire run() method synchronized and add the synchronized keyword; however, the author has not solved the situation where the log may be lost if he really wants to write it and the writing speed is fast enough;
Using Timer to implement is simpler, but if the tasks in Timer are executed for too long, they will exclusively occupy the Timer object, making the subsequent tasks unable to be executed at any time. The solution is also simpler. The thread pool version timer class ScheduledExecutorService is used to implement the following:
/** * */ package net.csdn.blog; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.log4j.FileAppender; import org.apache.log4j.Layout; import org.apache.log4j.helpers.LogLog; /** * @author coder_xia * <p> * Use ScheduledExecutorService to implement timed configuration printing logs* <p> * */ public class ScheduledExecutorServiceAppender extends FileAppender { /** * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" * meaning daily rollover. */ private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; /** * Interval time, unit: minutes*/ private int intervalTime = 10; SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN); /** * The default constructor does nothing. */ public ScheduledExecutorServiceAppender() { } /** * Instantiate a <code>ScheduledExecutorServiceAppender</code> and open the * file designed by <code>filename</code>. The opened filename will become * the ouput destination for this appender. */ public ScheduledExecutorServiceAppender(Layout layout, String filename) throws IOException { super(layout, filename, true); activateOptions(); } /** * @return the intervalTime */ public int getIntervalTime() { return intervalTime; } /** * @param intervalTime * the intervalTime to set */ public void setIntervalTime(int intervalTime) { this.intervalTime = intervalTime; } @Override public void activateOptions() { super.activateOptions(); Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate( new LogTimerTask(), 1, intervalTime * 60000, TimeUnit.MILLISECONDS); } class LogTimerTask implements Runnable { @Override public void run() { String datedFilename = fileName + sdf.format(new Date()); closeFile(); File target = new File(datedFilename); if (target.exists()) target.delete(); File file = new File(fileName); boolean result = file.renameTo(target); if (result) LogLog.debug(fileName + " -> " + datedFilename); else LogLog.error("Failed to rename [" + fileName + "] to [" + datedFilename + "]."); try { setFile(fileName, true, bufferedIO, bufferSize); } catch (IOException e) { errorHandler.error("setFile(" + fileName + ", true) call failed."); } } } } }Regarding the implementation of timing, this is almost the end. The default is to generate a new log file in 10 minutes. You can set it yourself when configuring it. However, there is a hidden danger. If the configuration person does not know that the time interval is minutes, if you think it is seconds, you have 600 and then open a debug to generate a log file with G, it will definitely be a disaster. The following transformation is to combine the maximum size of the RollingFileAppender and the maximum number of backup files to match, and then improve it again. Next time, we will continue to describe the transformation process.
Add module name configuration
I mentioned the custom class implementation of log4j timed printing, so I will not talk about the specified size and number of backup files. I can add it from the RollingFileAppender class copy code to the previous custom class. The only thing that needs to be solved is the concurrency problem, that is, when the rename file is closed, an error of output stream closed will be reported when a log event occurs.
There is now such an application scenario, and there are often:
1. The project contains multiple different projects;
2. The same project contains different modules.
For the first case, you can configure log4j<catogery="Test"> and then use the following method when generating Logger:
Logger logger=Logger.getLogger("Test");In the second case, we hope to print different modules into the same log file, but we hope to print out the module name in the log to locate the problem when there is a problem. Therefore, this article needs to add the configuration ModuleName to the Appender class. Let’s start the transformation below. Unlike timed printing, we use the RollingFileAppender class as the base class for transformation.
First, add the configuration item moduleName and add the get and set methods;
Since it is inherited from RollingFileAppender, you only need to format the data in LoggingEvent in subAppend(), add the formatInfo method to format the data, and the code is omitted;
The final product category is as follows:
package net.csdn.blog; import org.apache.log4j.Category; import org.apache.log4j.RollingFileAppender; import org.apache.log4j.spi.LoggingEvent; /** * @author coder_xia * */ public class ModuleAppender extends RollingFileAppender { private String moduleName; /** * @return the moduleName */ public String getModuleName() { return moduleName; } /** * @param moduleName * the moduleName to set */ public void setModuleName(String moduleName) { this.moduleName = moduleName; } /** * Format print content* * @param event * event * @return msg */ private String formatInfo(LoggingEvent event) { StringBuilder sb = new StringBuilder(); if (moduleName != null) { sb.append(moduleName).append("|"); sb.append(event.getMessage()); } return sb.toString(); } @Override public void subAppend(LoggingEvent event) { String msg = formatInfo(event); super.subAppend(new LoggingEvent(Category.class.getName(), event .getLogger(), event.getLevel(), msg, null)); } }