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

网友投稿 276 2023-07-22


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;

/**

* 按分钟可配置定时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 MinuteRollingAppender and open the file

* designated by filename. 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.

*

*

* 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();

}

}

}

测试配置文件如下:

关于定时实现,还可以采用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 TimerTaskRollingAppender and open the file

* designated by filename. 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

*

* 采用ScheduledExecutorService实现定时配置打印日志

*

*

*/

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 ScheduledExecutorServiceAppender and open the

* file designated by filename. 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,再在产生Logger时使用类似如下方式:

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

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

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

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

最终的成品类如下:

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

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:10个经典的Java main方法面试题
下一篇:Java的枚举类型使用方法详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~