添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
慷慨的包子  ·  python - The right ...·  1 年前    · 
留胡子的毛豆  ·  asp.net - How do I ...·  1 年前    · 
博学的紫菜  ·  python ...·  1 年前    · 
爱健身的跑步鞋  ·  android - what is the ...·  1 年前    · 
基于 Log4j 完成定时创建和删除日志的方法

1. 背景

最近要实现定期删除 N 天前的日志需求:

Log4j 作为常用的日志生成工具,其清除日志的策略却十分有限。只有在 RollingFileAppender 中可以通过设置 MaxFileSize maxBackupIndex 属性来指定要保留的日志文件大小以及个数,从而实现自动清除。

log4j删除N天前日志实现_log4j删除N天前日志

但是实际生产中,我们的真实的需求常常是定时每天生成一个日志文件,然后保留最近几天或近几个月的日志,历史日志需要及时清理。可是 Log4j 中的 DailyRollingFileAppender 这个类却不带属性 maxBackupIndex,maxFileSize 等,所以无法通过直接配置实现。

针对这种情况,可以是写一个定时删除日志的脚本,也可以是重新实现 DailyRollingFileAppender

这里我们通过继承 FileAppender ,重新实现 DailyRollingFileAppender 类,带有按时间顺序删除 N 天前的日志文件的功能。

2. 具体实现

2.1 代码实现

package com.demo.filter;

import java.io.File;

import java.io.FilenameFilter;

import java.io.IOException;

importjava.io.InterruptedIOException;

import java.io.Serializable;

import java.net.URI;

importjava.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Calendar;

import java.util.Collections;

import java.util.Date;

importjava.util.GregorianCalendar;

import java.util.List;

import java.util.Locale;

import java.util.TimeZone;

importorg.apache.log4j.FileAppender;

import org.apache.log4j.Layout;

importorg.apache.log4j.helpers.LogLog;

importorg.apache.log4j.spi.LoggingEvent;

/**

*

* @ 项目名称: wupao-job

* @ 类名称: CustomDailyRollingFileAppender

* @ 类描述: DailyRollingFileAppender extendsFileAppender

* </br> 重写 RollingFileAppender

* </br> 实现 maxBackupIndex 限制日志文件数量功能(定时删除超过 N 天前的日志文件)

* @ 创建人: wyait

* @ 创建时间: 2017 8 8 下午 2:40:38

* @version 1.0.0

*/

public classCustomDailyRollingFileAppender extends FileAppender {

// 初始化参数

staticfinal int TOP_OF_TROUBLE = -1;

staticfinal int TOP_OF_MINUTE = 0;

staticfinal int TOP_OF_HOUR = 1;

staticfinal int HALF_DAY = 2;

staticfinal int TOP_OF_DAY = 3;

staticfinal int TOP_OF_WEEK = 4;

staticfinal int TOP_OF_MONTH = 5;

/**

* 生产日志文件后缀 "'.'yyyy-MM-dd"

*/

privateString datePattern = "'.'yyyy-MM-dd";

/**

* 默认: 1 个日志文件

*/

protectedint maxBackupIndex = 1;

/**

* 保存前一天的日志文件名称 =filename+("'.'yyyy-MM-dd")

*/

privateString scheduledFilename;

//The next time we estimate a rollover should occur.

privatelong nextCheck = System.currentTimeMillis() - 1;

Datenow = new Date();

SimpleDateFormatsdf;

RollingCalendarrc = new RollingCalendar();

intcheckPeriod = TOP_OF_TROUBLE;

//The gmtTimeZone is used only in computeCheckPeriod() method.

staticfinal TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");

// 无参构造

publicCustomDailyRollingFileAppender() {

}

/** 有参构造

Instantiate a<code>DailyRollingFileAppender</code> and open the

file designated by<code>filename</code>. The opened filename will

become the ouput destination for thisappender.

*/

publicCustomDailyRollingFileAppender(Layout layout, String filename,

StringdatePattern) throws IOException {

super(layout,filename, true);

this.datePattern= datePattern;

activateOptions();

}

publicvoid setDatePattern(String pattern) {

datePattern= pattern;

}

publicvoid setMaxBackupIndex(int maxBackups) {

this.maxBackupIndex= maxBackups;

}

publicint getMaxBackupIndex() {

returnmaxBackupIndex;

}

publicString getDatePattern() {

returndatePattern;

}

@Override

publicvoid activateOptions() {

super.activateOptions();

if(datePattern != null && fileName != null) {

now.setTime(System.currentTimeMillis());

sdf= new SimpleDateFormat(datePattern);

inttype = computeCheckPeriod();

printPeriodicity(type);

rc.setType(type);

Filefile = new File(fileName);

scheduledFilename= fileName

+sdf.format(new Date(file.lastModified()));

}else {

LogLog.error("EitherFile or DatePattern options are not set for appender ["

+name + "].");

}

}

voidprintPeriodicity(int type) {

switch(type) {

caseTOP_OF_MINUTE:

LogLog.debug("Appender[" + name + "] to be rolled every minute.");

break;

caseTOP_OF_HOUR:

LogLog.debug("Appender[" + name

+"] to be rolled on top of every hour.");

break;

caseHALF_DAY:

LogLog.debug("Appender[" + name

+"] to be rolled at midday and midnight.");

break;

caseTOP_OF_DAY:

LogLog.debug("Appender[" + name + "] to be rolled at midnight.");

break;

caseTOP_OF_WEEK:

LogLog.debug("Appender[" + name

+"] to be rolled at start of week.");

break;

caseTOP_OF_MONTH:

LogLog.debug("Appender[" + name

+"] to be rolled at start of every month.");

break;

default:

LogLog.warn("Unknownperiodicity for appender [" + name + "].");

}

}

//This method computes the roll over period by looping over the

//periods, starting with the shortest, and stopping when the r0 is

//different from from r1, where r0 is the epoch formatted according

//the datePattern (supplied by the user) and r1 is the

//epoch+nextMillis(i) formatted according to datePattern. All date

//formatting is done in GMT and not local format because the test

//logic is based on comparisons relative to 1970-01-01 00:00:00

//GMT (the epoch).

intcomputeCheckPeriod() {

RollingCalendarrollingCalendar = new RollingCalendar(gmtTimeZone,

Locale.getDefault());

//set sate to 1970-01-01 00:00:00 GMT

Dateepoch = new Date(0);

if(datePattern != null) {

for(int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {

SimpleDateFormatsimpleDateFormat = new SimpleDateFormat(

datePattern);

simpleDateFormat.setTimeZone(gmtTimeZone);// do all date

//formatting in GMT

Stringr0 = simpleDateFormat.format(epoch);

rollingCalendar.setType(i);

Datenext = new Date(rollingCalendar.getNextCheckMillis(epoch));

Stringr1 = simpleDateFormat.format(next);

//System.out.println("Type = "+i+", r0 = "+r0+", r1 ="+r1);

if(r0 != null && r1 != null && !r0.equals(r1)) {

returni;

}

}

}

returnTOP_OF_TROUBLE; // Deliberately head for trouble...

}

/** 核心方法:生成日志文件,并完成对备份数量的监测以及历史日志的删除

Rollover the current file to a new file.

*/

voidrollOver() throws IOException {

// 获取所有日志历史文件:并完成对备份数量的监测以及历史日志的删除

List<ModifiedTimeSortableFile>files = getAllFiles();

Collections.sort(files);

if(files.size() >= maxBackupIndex) {

intindex = 0;

intdiff = files.size() - (maxBackupIndex - 1);

for(ModifiedTimeSortableFile file : files) {

if(index >= diff)

break;

file.delete();

index++;

}

}

/*Compute filename, but only if datePattern is specified */

if(datePattern == null) {

errorHandler.error("MissingDatePattern option in rollOver().");

return;

}

LogLog.debug("maxBackupIndex="+ maxBackupIndex);

StringdatedFilename = 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();

Filetarget = new File(scheduledFilename);

if(target.exists()) {

target.delete();

}

Filefile = new File(fileName);

booleanresult = file.renameTo(target);

if(result) {

LogLog.debug(fileName+ " -> " + scheduledFilename);

}else {

LogLog.error("Failedto 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 differentiatesDailyRollingFileAppender from its

* super class.

*

* <p>Before actually logging, thismethod will check whether it is

* time to do a rollover. If it is, it willschedule the next

* rollover time and then rollover.

* */

@Override

protectedvoid subAppend(LoggingEvent event) {

longn = 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);

}

/**

* 获取同一项目日志目录下所有文件

* This method searches list of log files

* based on the pattern given in the log4jconfiguration file

* and returns a collection

* @returnList&lt;ModifiedTimeSortableFile&gt;

*/

privateList<ModifiedTimeSortableFile> getAllFiles() {

List<ModifiedTimeSortableFile>files = new ArrayList<ModifiedTimeSortableFile>();

FilenameFilterfilter = new FilenameFilter() {

// 重写 accept 方法,确认是否是同一项目日志文件名称前缀。

@Override

publicboolean accept(File dir, String name) {

StringdirectoryName = dir.getPath();

LogLog.debug("directoryname: " + directoryName);

Filefile = new File(fileName);

StringperentDirectory = file.getParent();

if(perentDirectory != null) {

//name=demo.log

//directoryName= /logs

// 直接 fileName.substring(directoryName.length()) ;切割之后的 localFile=/demo.log… ,会导致 name.startsWith(localFile) 始终是 false

//so 解决方案:长度 +1

StringlocalFile = fileName.substring(directoryName

.length()+ 1);

returnname.startsWith(localFile);

}

returnname.startsWith(fileName);

}

};

Filefile = new File(fileName);

StringperentDirectory = file.getParent();

if(file.exists()) {

if(file.getParent() == null) {

StringabsolutePath = file.getAbsolutePath();

perentDirectory= absolutePath.substring(0,

absolutePath.lastIndexOf(fileName));

}

}

Filedir = new File(perentDirectory);

String[]names = dir.list(filter);

for(int i = 0; i < names.length; i++) {

files.add(newModifiedTimeSortableFile(dir

+System.getProperty("file.separator") + names[i]));

}

returnfiles;

}

}

/** 自定义 file 类,重写 compareTo 方法,比较文件的修改时间

* The ClassModifiedTimeSortableFile extends java.io.File class and

* implements Comparable to sortfiles list based upon their modified date

*/

class ModifiedTimeSortableFileextends File implements Serializable,

Comparable<File>{

privatestatic final long serialVersionUID = 1373373728209668895L;

publicModifiedTimeSortableFile(String parent, String child) {

super(parent,child);

//TODO Auto-generated constructor stub

}

publicModifiedTimeSortableFile(URI uri) {

super(uri);

//TODO Auto-generated constructor stub

}

publicModifiedTimeSortableFile(File parent, String child) {

super(parent,child);

}

publicModifiedTimeSortableFile(String string) {

super(string);

}

@Override

publicint compareTo(File anotherPathName) {

longthisVal = this.lastModified();

longanotherVal = anotherPathName.lastModified();

return(thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1));

}

}

/**

*  RollingCalendar is a helper class toDailyRollingFileAppender.

*  Given a periodicity type and the currenttime, it computes the

*  start of the next interval.

* */

class RollingCalendar extendsGregorianCalendar {

privatestatic final long serialVersionUID = -8295691444111406775L;

inttype = CustomDailyRollingFileAppender.TOP_OF_TROUBLE;

RollingCalendar(){

super();

}

RollingCalendar(TimeZonetz, Locale locale) {

super(tz,locale);

}

voidsetType(int type) {

this.type= type;

}

publiclong getNextCheckMillis(Date now) {

returngetNextCheckDate(now).getTime();

}

publicDate getNextCheckDate(Date now) {

this.setTime(now);

switch(type) {

caseCustomDailyRollingFileAppender.TOP_OF_MINUTE:

this.set(Calendar.SECOND,0);

this.set(Calendar.MILLISECOND,0);

this.add(Calendar.MINUTE,1);

break;

caseCustomDailyRollingFileAppender.TOP_OF_HOUR:

this.set(Calendar.MINUTE,0);

this.set(Calendar.SECOND,0);

this.set(Calendar.MILLISECOND,0);

this.add(Calendar.HOUR_OF_DAY,1);

break;

caseCustomDailyRollingFileAppender.HALF_DAY:

this.set(Calendar.MINUTE,0);

this.set(Calendar.SECOND,0);

this.set(Calendar.MILLISECOND,0);

inthour = get(Calendar.HOUR_OF_DAY);

if(hour < 12) {

this.set(Calendar.HOUR_OF_DAY,12);

}else {

this.set(Calendar.HOUR_OF_DAY,0);

this.add(Calendar.DAY_OF_MONTH,1);

}

break;

caseCustomDailyRollingFileAppender.TOP_OF_DAY:

this.set(Calendar.HOUR_OF_DAY,0);

this.set(Calendar.MINUTE,0);

this.set(Calendar.SECOND,0);

this.set(Calendar.MILLISECOND,0);

this.add(Calendar.DATE,1);

break;

caseCustomDailyRollingFileAppender.TOP_OF_WEEK:

this.set(Calendar.DAY_OF_WEEK,getFirstDayOfWeek());

this.set(Calendar.HOUR_OF_DAY,0);

this.set(Calendar.MINUTE,0);

this.set(Calendar.SECOND,0);

this.set(Calendar.MILLISECOND,0);

this.add(Calendar.WEEK_OF_YEAR,1);

break;

caseCustomDailyRollingFileAppender.TOP_OF_MONTH:

this.set(Calendar.DATE,1);

this.set(Calendar.HOUR_OF_DAY,0);

this.set(Calendar.MINUTE,0);

this.set(Calendar.SECOND,0);

this.set(Calendar.MILLISECOND,0);

this.add(Calendar.MONTH,1);

break;

default:

thrownew IllegalStateException("Unknown periodicity type.");

}

returngetTime();

}

}

2.2 配置实现

resources 目录下,自定义 log4j.xml 日志配置文件:

<?xml version="1.0"encoding="UTF-8"?>

<!DOCTYPE log4j:configurationSYSTEM "log4j.dtd" >

<log4j:configuration>

<!-- 引用自定义的 CustomDailyRollingFileAppender 日志类 -->

<appender name="FILE"class="com.demo.filter.CustomDailyRollingFileAppender">

<!-- 自定义日志输出路径和名称 -->

<param name="file"value="D:/logs/test-agent.log" />

<!-- 自定义历史日志文件后缀名 -->

<param name="datePattern"value="'.'yyyy-MM-dd" />

<!-- 自定义日志文件总数量,大于这个数量,自动删除 -->

<param name="maxBackupIndex"value="3" />

<param name="append"value="true" />

<layoutclass="org.apache.log4j.PatternLayout">

<paramname="ConversionPattern" value="%d [%t] %p - %m%n" />

</layout>

</appender>

<root>

<priority value="info" />

<appender-ref ref="FILE"/>

</root>

</log4j:configuration>

2.3 测试类

main 方法:

public static voidmain1(String[] args) {

finalFile file = new File("log4j.xml");

if(file.exists()) {

finalURL url = Main.class.getClassLoader()

.getResource("log4j.xml");

StringconfigFile = url.getPath();

PropertyConfigurator.configure("log4j.xml");

}

log.trace("Trace");

log.debug("Debug");

log.info("Info");

log.warn("Warn");

log.error("Error");

log.fatal("Fatal");

}

第一次启动:

log4j删除N天前日志实现_log4j_02

手动创建几个其他日期的历史文件:

log4j删除N天前日志实现_log4j删除N天前日志_03

第二次启动:

log4j删除N天前日志实现_log4j_04

2.4 项目 log4j 实战配置

log4j.rootLogger=INFO,demo

log4j.logger.org.mybatis = INFO

log4j.appender.demo=org.apache.log4j.ConsoleAppender

log4j.appender.demo.layout=org.apache.log4j.PatternLayout

log4j.appender.demo.layout.ConversionPattern=%m%n

log4j.appender.demo=com.demo.filter.CustomDailyRollingFileAppender

#log4j.appender.demo=org.apache.log4j.RollingFileAppender

log4j.appender.demo.File=D:/logs/demo.log

#log4j.appender.demo.Threshold=INFO

#log4j.appender.demo.ImmediateFlush=true

#log4j.appender.demo.Append=true

log4j.appender.demo.maxBackupIndex=3

log4j.appender.demo.layout.ConversionPattern=[%d{yyyy:MM:dd HH:mm:ss}]:%m \r\n

log4j.appender.demoConsole.encoding=utf-8

log4j.appender.demoConsole=org.apache.log4j.ConsoleAppender

log4j.appender.demoConsole.Target= System.out

log4j.appender.demoConsole.layout=org.apache.log4j.PatternLayout

log4j.appender.demoConsole.layout.ConversionPattern= [%d{yyyy:MM:dd HH:mm:ss}]:%m \r\n

3.Log4j 相关配置的含义

Log4j由三个重要的组件构成:日志信息的优先级,日志信息的输出目的地,日志信息的输出格式。日志信息的优先级从高到低有ERROR、WARN、 INFO、DEBUG,分别用来指定这条日志信息的重要程度;日志信息的输出目的地指定了日志将打印到控制台还是文件中;而输出格式则控制了日志信息的显示内容。Log4j支持两种配置文件格式,一种是XML格式的文件,一种是Java特性文件(键=值)。这里,我们主要探讨基于XML的配置方式。

3.1 配置根 logger

基本语法是:

log4j删除N天前日志实现_log4j定时清理日志_05

其中, level 是日志记录的优先级,分为 OFF FATAL ERROR WARN INFO DEBUG ALL 或者您定义的级别。 Log4j 建议只使用四个级别,优 先级从高到低分别是 ERROR WARN INFO DEBUG

这里,每一个 appenderName 均是下面需要配置的日志信息的名称。

实际例子:

log4j.rootLogger=INFO,stdout, ROLLING_ERROR_FILE, ROLLING_INFO_FILE

log4j删除N天前日志实现_log4j_06

3.2 配置日志信息输出目的地 Appeder

Log4j 中提供的 Appender 主要有以下几种:

org.apache.log4j.ConsoleAppender (控制台),

org.apache.log4j.FileAppender (文件),

org.apache.log4j.DailyRollingFileAppender (每天产生一个日志文件),

org.apache.log4j.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新的文件),

org.apache.log4j.WriterAppender (将日志信息以流格式发送到任意指定的地方)

基本语法为:

log4j删除N天前日志实现_log4j删除N天前日志_07

实际例子:

log4j.appender.ROLLING_ERROR_FILE=org.apache.log4j.DailyRollingFileAppender

针对不同的 Appender ,它们的属性也有一定的差别,这些属性主要是针对日志文件的命名规则、保存路径、删除策略等有关系。

3.2.1 ConsoleAppender 的属性

Threshold=WARN :指定日志信息的最低输出级别,默认为 DEBUG

ImmediateFlush=true :表示所有消息都会被立即输出,设为 false 则不输出,默认值是 true

Target=System.err :默认值是 System.out

3.2.2 FileAppender 的属性

Threshold=WARN :指定日志信息的最低输出级别,默认为 DEBUG

ImmediateFlush=true :表示所有消息都会被立即输出,设为 false 则不输出,默认值是 true

Append=false true 表示消息增加到指定文件中, false 则将消息覆盖指定的文件内容,默认值是 true

File=D:/logs/logging.log4j :指定消息输出到 logging.log4j 文件中。

3.2.3 DailyRollingFileAppender 的属性

Threshold=WARN :指定日志信息的最低输出级别,默认为 DEBUG

ImmediateFlush=true :表示所有消息都会被立即输出,设为 false 则不输出,默认值是 true

Append=false true 表示消息增加到指定文件中, false 则将消息覆盖指定的文件内容,默认值是 true

File=D:/logs/logging.log4j :指定当前消息输出到 logging.log4j 文件中。

DatePattern='.'yyyy-MM :每月滚动一次日志文件,即每月产生一个新的日志文件。当前月的日志文件名为 logging.log4j ,前一个月的日志文件名为 logging.log4j.yyyy-MM

另外,也可以指定按周、天、时、分等来滚动日志文件,对应的格式如下:

1)'.'yyyy-MM :每月

2)'.'yyyy-ww :每周

3)'.'yyyy-MM-dd :每天

4)'.'yyyy-MM-dd-a :每天两次

5)'.'yyyy-MM-dd-HH :每小时

6)'.'yyyy-MM-dd-HH-mm :每分钟

3.2.4 RollingFileAppender 的属性

Threshold=WARN :指定日志信息的最低输出级别,默认为 DEBUG

ImmediateFlush=true :表示所有消息都会被立即输出,设为 false 则不输出,默认值是 true

Append=false true 表示消息增加到指定文件中, false 则将消息覆盖指定的文件内容,默认值是 true

File=D:/logs/logging.log4j :指定消息输出到 logging.log4j 文件中。

MaxFileSize=100KB :后缀可以是 KB, MB 或者 GB 。在日志文件到达该大小时,将会自动滚动,即将原来的内容移到 logging.log4j.1 文件中。

MaxBackupIndex=2 :指定可以产生的滚动文件的最大数,例如,设为 2 则可以产生 logging.log4j.1 logging.log4j.2 两个滚动文件和一个 logging.log4j 文件。

3.3 配置日志信息的格式(布局) layout

Log4j 提供的 layout 有以下几种:

org.apache.log4j.HTMLLayout (以 HTML 表格形式布局),

org.apache.log4j.PatternLayout (可以灵活地指定布局模式),

org.apache.log4j.SimpleLayout (包含日志信息的级别和信息字符串),

org.apache.log4j.TTCCLayout (包含日志产生的时间、线程、类别等等信息)

Log4j 采用类似 C 语言中的 printf 函数的打印格式格式化日志信息,打印参数如下:

%m 输出代码中指定的消息

%p 输出优先级,即 DEBUG INFO WARN ERROR FATAL

%r 输出自应用启动到输出该 log 信息耗费的毫秒数

%c 输出所属的类目,通常就是所在类的全名

%t 输出产生该日志事件的线程名

%n 输出一个回车换行符, Windows 平台为 “rn” Unix 平台为 “n”

%d 输出日志时间点的日期或时间,默认格式为 ISO8601 ,也可以在其后指定格式,比如: %d{yyy MMM dd HH:mm:ss,SSS} ,输出类似: 2002 10 18 22 10 28 921

%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例: Testlog4.main(TestLog4.java:10)

基本语法为:

log4j删除N天前日志实现_log4j_08

实际例子:

log4j.appender.ROLLING_ERROR_FILE.layout=org.apache.log4j.PatternLayout

log4j.appender.ROLLING_ERROR_FILE.layout.ConversionPattern=[log]%d -%-4r [%t] %c %x%n %-5p - %m [%l] %n