Log4j prints logs regularly and adds Java code instances configured by module names

  • 2020-04-01 04:38:22
  • OfStack

Configure intervals to print logs on a regular basis
  After receiving a requirement, log can be printed regularly through log4j. The requirement is described as follows: log can be printed regularly, and the time interval can be matched. Speaking of timing, the first thought of the DailyRollingFileAppender class, various timing, according to the datePattern, this can refer to the class SimpleDateFormat class, some common timing Settings are as follows:

'. ': yyyy - MM   a month '. 'yyyy - ww: weekly     '. '- dd yyyy - MM: every day   'yyyy-mm-dd-a: twice a day   '.' yyyy-mm-dd-hh: hourly   Yyyy-mm-dd-hh-mm: every minute    

      By observing that there is no similar date format for n minutes, the custom classes are written on the basis of the DailyRollingFileAppender class. The process is as follows:

  1) copy the DailyRollingFileAppender source code and rename it MinuteRollingAppender. In order to configure it in log4j.xml, add the configuration item intervalTime and add set and get methods;


private int intervalTime = 10; 

  2) since the DailyRollingFileAppender class USES the RollingCalendar class to calculate the next intervalTime and needs to pass the parameter intervalTime, the RollingCalendar class is modified to be an inner class; Since the method is to calculate the time of the next rollOver action according to the 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 it is available in minutes, the time mode needs to be disabled, changed to static final, and its get and set methods and datePattern parameters in the MinuteRollingAppender constructor are removed in response


private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 

      Likewise, the method computeCheckPeriod() that serves multiple datepatterns can be deleted; So far, the transformation has been completed. The finished products are 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; 
 
 
public class MinuteRollingAppender extends FileAppender 
{ 
  
 private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 
  
 private int intervalTime = 10; 
 
  
 private String scheduledFilename; 
 
  
 private long nextCheck = System.currentTimeMillis() - 1; 
 
 Date now = new Date(); 
 
 SimpleDateFormat sdf; 
 
 RollingCalendar rc = new RollingCalendar(); 
 
  
 public MinuteRollingAppender() 
 { 
 } 
 
 /** 
  * Instantiate a <code>MinuteRollingAppender</code> and open the file 
  * designated 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(); 
 } 
 
  
 public int getIntervalTime() 
 { 
  return intervalTime; 
 } 
 
  
 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 + "]."); 
  } 
 } 
 
  
 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; 
 } 
 
  
 @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); 
 } 
 
  
 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" class="net.csdn.blog.MinuteRollingAppender">  
  <param name="File" value="log4jTest.log" /> 
  <param name="Append" value="true" /> 
  <param name="intervalTime" value="2"/> 
  <layout class="org.apache.log4j.PatternLayout"> 
   <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, we can also use the Timer implementation provided by Java, which eliminates the need to calculate and compare the time every time when logging. The difference is actually to create a thread and call the rollOver method, 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 
{ 
  
 private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 
 
  
 private int intervalTime = 10; 
 
 SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN); 
 
  
 public TimerTaskRollingAppender() 
 { 
 } 
 
 /** 
  * Instantiate a <code>TimerTaskRollingAppender</code> and open the file 
  * designated 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(); 
 } 
 
  
 public int getIntervalTime() 
 { 
  return intervalTime; 
 } 
 
  
 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) concurrent

      One place where concurrency problems can occur is to call closeFile() in run(). When the subAppend() method writes the log, the file is closed and the following error is 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 simple: make the entire run() method synchronized by adding the synchronized keyword. However, the current building owner did not solve if you really want to write, and write the speed of the case may lose the log;
    2) performance

      It is relatively simple to implement with Timer, but the tasks in the Timer will monopolize the Timer object if the execution time is too long, making the subsequent tasks unable to execute for a long time. The solution is also relatively simple. The thread pool version of Timer class ScheduledExecutorService is adopted 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; 
 
 
public class ScheduledExecutorServiceAppender extends FileAppender 
{ 
  
 private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 
 
  
 private int intervalTime = 10; 
 
 SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN); 
 
  
 public ScheduledExecutorServiceAppender() 
 { 
 } 
 
 /** 
  * Instantiate a <code>ScheduledExecutorServiceAppender</code> and open the 
  * file designated 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(); 
 } 
 
  
 public int getIntervalTime() 
 { 
  return intervalTime; 
 } 
 
  
 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."); 
   } 
  } 
 } 
} 

          About the timing of the implementation, almost here, the default is 10 minutes to create a new log files, to be in the configuration, but there is a hidden trouble, one thousand configuration does not know time interval is minutes, if thought is second, with a 600, and opened the debug, G on the log file, it must have been a disaster, reform of the following is the combination of RollingFileAppender maximum size and the highest number of backup files can match, perfect again, next time continue to describe the transformation process.

Add the module name configuration
In the previous section, we talked about the custom class implementation of log4j timing printing. Instead of specifying the size and number of backup files, we can add the copy code from the RollingFileAppender class to the previous custom class. The only problem that needs to be solved is the concurrency problem, that is, when the file closes the rename file, the output stream closes when the log event occurs.

      There is now an application scenario, and it often has:

      1. The project includes several different projects;

      2. The same project contains different modules.

      For the first case, you can configure log4j< Catogery = "Test" > , and then, when Logger is generated, use the following method:


Logger logger=Logger.getLogger("Test"); 

      About the second case, we hope to be able to get a different modules to print to the same log file, but hope to be able to print out in the log module name for positioning problem at the time of the problem, therefore had the need to be added in the Appender class configuration ModuleName, below began to transform, unlike regular print, we adopt RollingFileAppender class as a base class for modification.

      First, add the configuration item moduleName and add the get and set methods.

      Since it is inherited from the RollingFileAppender, you only need to format the data in the LoggingEvent in the subAppend(), and add the formatInfo method to format the data, the code is omitted;

      The final products are as follows:


package net.csdn.blog; 
 
import org.apache.log4j.Category; 
import org.apache.log4j.RollingFileAppender; 
import org.apache.log4j.spi.LoggingEvent; 
 
 
public class ModuleAppender extends RollingFileAppender 
{ 
 private String moduleName; 
 
  
 public String getModuleName() 
 { 
  return moduleName; 
 } 
 
  
 public void setModuleName(String moduleName) 
 { 
  this.moduleName = moduleName; 
 } 
 
  
 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)); 
 } 
} 


Related articles: