Log4j定時(shí)打印日志及添加模塊名配置的Java代碼實(shí)例
配置間隔時(shí)間,定時(shí)打印日志
接到個(gè)需求,通過log4j定時(shí)打印日志,需求描述如下:需要能夠定時(shí)打印日志,時(shí)間間隔可配。說到定時(shí),首先想到了DailyRollingFileAppender類,各種定時(shí),根據(jù)datePattern,這個(gè)可以參考類SimpleDateFormat類,常見的一些定時(shí)設(shè)置如下:
- '.'yyyy-MM: 每月
- '.'yyyy-ww: 每周
- '.'yyyy-MM-dd: 每天
- '.'yyyy-MM-dd-a: 每天兩次
- '.'yyyy-MM-dd-HH: 每小時(shí)
- '.'yyyy-MM-dd-HH-mm: 每分鐘
通過觀察發(fā)現(xiàn)沒有n分鐘類似的日期格式,因此,在DailyRollingFileAppender類基礎(chǔ)上進(jìn)行自定義類的編寫。過程如下:
1)拷貝DailyRollingFileAppender類源碼并并改名MinuteRollingAppender,為了在log4j.xml中配置,增加配置項(xiàng)intervalTime并添加set、get方法;
private int intervalTime = 10;
2)由于DailyRollingFileAppender類使用了RollingCalendar類來計(jì)算下一次間隔時(shí)間,而需要傳遞參數(shù)intervalTime,因此修改RollingCalendar類為內(nèi)部類;由于其方法就是根據(jù)datePattern來計(jì)算下一次rollOver動(dòng)作的時(shí)間,此時(shí)不需要其他的時(shí)間模式,修改方法如下:
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)按照分鐘可配時(shí),時(shí)間模式就需要禁用了,將其改為static final,響應(yīng)的去掉其get、set方法和MinuteRollingAppender構(gòu)造函數(shù)中的datePattern參數(shù)
private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";
同樣,服務(wù)于多種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;
/**
* 按分鐘可配置定時(shí)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'";
/**
* 間隔時(shí)間,單位:分鐘
*/
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();
}
}
}
測(cè)試配置文件如下:
<?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>
關(guān)于定時(shí)實(shí)現(xiàn),還可以采用java提供的Timer實(shí)現(xiàn),也就免去了每次記錄日志時(shí)計(jì)算并且比較時(shí)間,區(qū)別其實(shí)就是自己起個(gè)線程與調(diào)用rollOver方法,實(shí)現(xiàn)如下:
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'";
/**
* 間隔時(shí)間,單位:分鐘
*/
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.");
}
}
}
}
不過,以上實(shí)現(xiàn),存在2個(gè)問題:
1)并發(fā)
并發(fā)問題可能發(fā)生的一個(gè)地方在run()中調(diào)用closeFile();后,正好subAppend()方法寫日志,此刻文件已關(guān)閉,則會(huì)報(bào)以下錯(cuò)誤:
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) ..............................
2)性能
使用Timer實(shí)現(xiàn)比較簡(jiǎn)單,但是Timer里面的任務(wù)如果執(zhí)行時(shí)間太長(zhǎng),會(huì)獨(dú)占Timer對(duì)象,使得后面的任務(wù)無法幾時(shí)的執(zhí)行,解決方法也比較簡(jiǎn)單,采用線程池版定時(shí)器類ScheduledExecutorService,實(shí)現(xiàn)如下:
/**
*
*/
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實(shí)現(xiàn)定時(shí)配置打印日志
* <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'";
/**
* 間隔時(shí)間,單位:分鐘
*/
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.");
}
}
}
}
關(guān)于定時(shí)的實(shí)現(xiàn),差不多就到這里了,采用的都默認(rèn)是10分鐘產(chǎn)生一個(gè)新的日志文件,在配置時(shí)可以自行設(shè)置,不過存在的一個(gè)隱患,萬一配置的人不知道時(shí)間間隔是分鐘,如果以為是秒,配了個(gè)600,又開了debug,產(chǎn)生上G的日志文件,這肯定是個(gè)災(zāi)難,下面的改造就是結(jié)合RollingFileAppender的最大大小和最多備份文件個(gè)數(shù)可配,再次進(jìn)行完善,下次繼續(xù)描述改造過程。
添加模塊名配置
在前面講到了log4j定時(shí)打印的定制類實(shí)現(xiàn),就不講指定大小和指定備份文件個(gè)數(shù)了,從RollingFileAppender類copy代碼到前面的定制類中添加即可,唯一需要解決的是并發(fā)問題,即文件關(guān)閉rename文件時(shí),發(fā)生了記錄日志事件時(shí),會(huì)報(bào)output stream closed的錯(cuò)誤。
現(xiàn)在有這樣一種應(yīng)用場(chǎng)景,而且經(jīng)常有:
1.項(xiàng)目包含有多個(gè)不同的工程;
2.同一工程包含不同的模塊。
對(duì)第一種情況,可以通過配置log4j<catogery=“Test”>,再在產(chǎn)生Logger時(shí)使用類似如下方式:
Logger logger=Logger.getLogger("Test");
對(duì)第二種情況,我們希望能夠?qū)⒉煌K打印到同一個(gè)日志文件中,不過希望能夠在日志中打印出模塊名以便出問題時(shí)定位問題,因此便有了本文需要的在Appender類中添加配置ModuleName,下面開始改造,與定時(shí)打印不同,我們采用RollingFileAppender類為基類進(jìn)行改造。
首先,添加配置項(xiàng)moduleName,并增加get、set方法;
由于繼承自RollingFileAppender,所以只需要在subAppend()中格式化LoggingEvent中的數(shù)據(jù),添加formatInfo方法格式化數(shù)據(jù),代碼略;
最終的成品類如下:
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;
}
/**
* 格式化打印內(nèi)容
*
* @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));
}
}
相關(guān)文章
Java中的ReentrantLock實(shí)現(xiàn)原理及代碼演示
這篇文章主要介紹了Java中的ReentrantLock實(shí)現(xiàn)原理及代碼演示,非公平鎖 如果已經(jīng)進(jìn)入隊(duì)列,鏈表里面的線程是先進(jìn)先出,如果已經(jīng)釋放了鎖,在搶占鎖時(shí),鏈表里面的頭結(jié)點(diǎn)和還沒有入隊(duì)列的線程搶鎖,需要的朋友可以參考下2024-01-01
Java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(借助Array?List)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),借助Array?List,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
一行命令同時(shí)修改maven項(xiàng)目中多個(gè)module的版本號(hào)的方法
這篇文章主要介紹了一行命令同時(shí)修改maven項(xiàng)目中多個(gè)module的版本號(hào)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-06-06
MybatisPlus整合Flowable出現(xiàn)的坑及解決
這篇文章主要介紹了MybatisPlus整合Flowable出現(xiàn)的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
java微信掃碼支付模式一線下支付功能實(shí)現(xiàn)
本篇文章主要介紹了JAVA微信掃碼支付模式一線下支付功能實(shí)現(xiàn),具有一定的參考價(jià)值,有需要的可以了解一下。2016-11-11
MyBatis中多對(duì)一和一對(duì)多數(shù)據(jù)的處理方法
這篇文章主要介紹了MyBatis中多對(duì)一和一對(duì)多數(shù)據(jù)的處理,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01
java多線程通過CompletableFuture組裝異步計(jì)算單元
這篇文章主要為大家介紹了java多線程通過CompletableFuture組裝異步計(jì)算單元,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04

