栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Log4j定时打印日志及添加模块名配置的Java代码实例

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Log4j定时打印日志及添加模块名配置的Java代码实例

配置间隔时间,定时打印日志
 接到个需求,通过log4j定时打印日志,需求描述如下:需要能够定时打印日志,时间间隔可配。说到定时,首先想到了DailyRollingFileAppender类,各种定时,根据datePattern,这个可以参考类SimpleDateFormat类,常见的一些定时设置如下:

  • '.'yyyy-MM: 每月 
  • '.'yyyy-ww: 每周  
  • '.'yyyy-MM-dd: 每天 
  • '.'yyyy-MM-dd-a: 每天两次 
  • '.'yyyy-MM-dd-HH: 每小时 
  • '.'yyyy-MM-dd-HH-mm: 每分钟  

    通过观察发现没有n分钟类似的日期格式,因此,在DailyRollingFileAppender类基础上进行自定义类的编写。过程如下:

  1)拷贝DailyRollingFileAppender类源码并并改名MinuteRollingAppender,为了在log4j.xml中配置,增加配置项intervalTime并添加set、get方法;

private int intervalTime = 10; 

  2)由于DailyRollingFileAppender类使用了RollingCalendar类来计算下一次间隔时间,而需要传递参数intervalTime,因此修改RollingCalendar类为内部类;由于其方法就是根据datePattern来计算下一次rollOver动作的时间,此时不需要其他的时间模式,修改方法如下:

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)按照分钟可配时,时间模式就需要禁用了,将其改为static final,响应的去掉其get、set方法和MinuteRollingAppender构造函数中的datePattern参数

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

    同样,服务于多种datePattern的方法computeCheckPeriod()也可以删除; 至此改造就完成了,成品类如下:

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() 
 { 
 } 
 
  
 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(); 
  } 
 } 
} 

测试配置文件如下:

 
 
 
 
 
   
   
   
   
   
    
   
  
 
  
   
    
  
 
 

      关于定时实现,还可以采用java提供的Timer实现,也就免去了每次记录日志时计算并且比较时间,区别其实就是自己起个线程与调用rollOver方法,实现如下:

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() 
 { 
 } 
 
  
 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."); 
   } 
  } 
 } 
} 

    不过,以上实现,存在2个问题:

   1)并发

    并发问题可能发生的一个地方在run()中调用closeFile();后,正好subAppend()方法写日志,此刻文件已关闭,则会报以下错误:

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) 
.............................. 

   解决方法比较简单,直接让整个run()方法为同步的,加上synchronized关键字即可;不过目前楼主没有解决如果真要写,而且写的速度够快的情况下可能丢失日志的情况;
   2)性能

    使用Timer实现比较简单,但是Timer里面的任务如果执行时间太长,会独占Timer对象,使得后面的任务无法几时的执行,解决方法也比较简单,采用线程池版定时器类ScheduledExecutorService,实现如下:

 
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() 
 { 
 } 
 
  
 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."); 
   } 
  } 
 } 
} 

      关于定时的实现,差不多就到这里了,采用的都默认是10分钟产生一个新的日志文件,在配置时可以自行设置,不过存在的一个隐患,万一配置的人不知道时间间隔是分钟,如果以为是秒,配了个600,又开了debug,产生上G的日志文件,这肯定是个灾难,下面的改造就是结合RollingFileAppender的最大大小和最多备份文件个数可配,再次进行完善,下次继续描述改造过程。

添加模块名配置
在前面讲到了log4j定时打印的定制类实现,就不讲指定大小和指定备份文件个数了,从RollingFileAppender类copy代码到前面的定制类中添加即可,唯一需要解决的是并发问题,即文件关闭rename文件时,发生了记录日志事件时,会报output stream closed的错误。

    现在有这样一种应用场景,而且经常有:

    1.项目包含有多个不同的工程;

    2.同一工程包含不同的模块。

    对第一种情况,可以通过配置log4j,再在产生Logger时使用类似如下方式:

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

    对第二种情况,我们希望能够将不同模块打印到同一个日志文件中,不过希望能够在日志中打印出模块名以便出问题时定位问题,因此便有了本文需要的在Appender类中添加配置ModuleName,下面开始改造,与定时打印不同,我们采用RollingFileAppender类为基类进行改造。

    首先,添加配置项moduleName,并增加get、set方法;

    由于继承自RollingFileAppender,所以只需要在subAppend()中格式化LoggingEvent中的数据,添加formatInfo方法格式化数据,代码略;

    最终的成品类如下:

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)); 
 } 
} 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/150640.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号