log4j定制类实现(一):配置间隔时间,定时打印日志

    接到个需求,通过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,有很多比较好的log4j的参考链接,比如:

1.http://www.iteye.com/topic/378077

2.http://www.cnblogs.com/duanxz/archive/2013/01/28/2880240.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值