当前位置: 移动技术网 > IT编程>开发语言>Java > Log4j定时打印日志及添加模块名配置的Java代码实例

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

2019年07月22日  | 移动技术网IT编程  | 我要评论

配置间隔时间,定时打印日志
 接到个需求,通过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; 
 
/** 
 * 按分钟可配置定时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'"; 
 /** 
  * 间隔时间,单位:分钟 
  */ 
 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 precise time when a rollover occurs depends 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 
  * 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(); 
 } 
 
 /** 
  * @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(); 
  } 
 } 
} 

测试配置文件如下:

<?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> 

      关于定时实现,还可以采用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 
{ 
 /** 
  * 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'"; 
 
 /** 
  * 间隔时间,单位:分钟 
  */ 
 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 
  * 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(); 
 } 
 
 /** 
  * @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."); 
   } 
  } 
 } 
} 

    不过,以上实现,存在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; 
 
/** 
 * @author coder_xia 
 *   <p> 
 *   采用scheduledexecutorservice实现定时配置打印日志 
 *   <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'"; 
 
 /** 
  * 间隔时间,单位:分钟 
  */ 
 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 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(); 
 } 
 
 /** 
  * @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."); 
   } 
  } 
 } 
} 

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

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

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

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

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

    对第一种情况,可以通过配置log4j<catogery=“test”>,再在产生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; 
 
/** 
 * @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; 
 } 
 
 /** 
  * 格式化打印内容 
  * 
  * @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)); 
 } 
} 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网