Cloud.Liu
4 years ago
1 changed files with 503 additions and 0 deletions
@ -0,0 +1,503 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
package com.fr.third.apache.log4j; |
||||||
|
|
||||||
|
import com.fr.third.apache.log4j.helpers.LogLog; |
||||||
|
import com.fr.third.apache.log4j.spi.LoggingEvent; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.FileWriter; |
||||||
|
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 java.util.Locale; |
||||||
|
import java.util.TimeZone; |
||||||
|
import java.util.zip.GZIPOutputStream; |
||||||
|
|
||||||
|
/** |
||||||
|
DailyRollingFileAppender extends {@link FileAppender} so that the |
||||||
|
underlying file is rolled over at a user chosen frequency. |
||||||
|
|
||||||
|
DailyRollingFileAppender has been observed to exhibit |
||||||
|
synchronization issues and data loss. The log4j extras |
||||||
|
companion includes alternatives which should be considered |
||||||
|
for new deployments and which are discussed in the documentation |
||||||
|
for RollingFileAppender. |
||||||
|
|
||||||
|
<p>The rolling schedule is specified by the <b>DatePattern</b> |
||||||
|
option. This pattern should follow the {@link SimpleDateFormat} |
||||||
|
conventions. In particular, you <em>must</em> escape literal text |
||||||
|
within a pair of single quotes. A formatted version of the date |
||||||
|
pattern is used as the suffix for the rolled file name. |
||||||
|
|
||||||
|
<p>For example, if the <b>File</b> option is set to |
||||||
|
<code>/foo/bar.log</code> and the <b>DatePattern</b> set to |
||||||
|
<code>'.'yyyy-MM-dd</code>, on 2001-02-16 at midnight, the logging |
||||||
|
file <code>/foo/bar.log</code> will be copied to |
||||||
|
<code>/foo/bar.log.2001-02-16</code> and logging for 2001-02-17 |
||||||
|
will continue in <code>/foo/bar.log</code> until it rolls over |
||||||
|
the next day. |
||||||
|
|
||||||
|
<p>Is is possible to specify monthly, weekly, half-daily, daily, |
||||||
|
hourly, or minutely rollover schedules. |
||||||
|
|
||||||
|
<p><table border="1" cellpadding="2"> |
||||||
|
<tr> |
||||||
|
<th>DatePattern</th> |
||||||
|
<th>Rollover schedule</th> |
||||||
|
<th>Example</th> |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><code>'.'yyyy-MM</code> |
||||||
|
<td>Rollover at the beginning of each month</td> |
||||||
|
|
||||||
|
<td>At midnight of May 31st, 2002 <code>/foo/bar.log</code> will be |
||||||
|
copied to <code>/foo/bar.log.2002-05</code>. Logging for the month |
||||||
|
of June will be output to <code>/foo/bar.log</code> until it is |
||||||
|
also rolled over the next month. |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><code>'.'yyyy-ww</code> |
||||||
|
|
||||||
|
<td>Rollover at the first day of each week. The first day of the |
||||||
|
week depends on the locale.</td> |
||||||
|
|
||||||
|
<td>Assuming the first day of the week is Sunday, on Saturday |
||||||
|
midnight, June 9th 2002, the file <i>/foo/bar.log</i> will be |
||||||
|
copied to <i>/foo/bar.log.2002-23</i>. Logging for the 24th week |
||||||
|
of 2002 will be output to <code>/foo/bar.log</code> until it is |
||||||
|
rolled over the next week. |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><code>'.'yyyy-MM-dd</code> |
||||||
|
|
||||||
|
<td>Rollover at midnight each day.</td> |
||||||
|
|
||||||
|
<td>At midnight, on March 8th, 2002, <code>/foo/bar.log</code> will |
||||||
|
be copied to <code>/foo/bar.log.2002-03-08</code>. Logging for the |
||||||
|
9th day of March will be output to <code>/foo/bar.log</code> until |
||||||
|
it is rolled over the next day. |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><code>'.'yyyy-MM-dd-a</code> |
||||||
|
|
||||||
|
<td>Rollover at midnight and midday of each day.</td> |
||||||
|
|
||||||
|
<td>At noon, on March 9th, 2002, <code>/foo/bar.log</code> will be |
||||||
|
copied to <code>/foo/bar.log.2002-03-09-AM</code>. Logging for the |
||||||
|
afternoon of the 9th will be output to <code>/foo/bar.log</code> |
||||||
|
until it is rolled over at midnight. |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><code>'.'yyyy-MM-dd-HH</code> |
||||||
|
|
||||||
|
<td>Rollover at the top of every hour.</td> |
||||||
|
|
||||||
|
<td>At approximately 11:00.000 o'clock on March 9th, 2002, |
||||||
|
<code>/foo/bar.log</code> will be copied to |
||||||
|
<code>/foo/bar.log.2002-03-09-10</code>. Logging for the 11th hour |
||||||
|
of the 9th of March will be output to <code>/foo/bar.log</code> |
||||||
|
until it is rolled over at the beginning of the next hour. |
||||||
|
|
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><code>'.'yyyy-MM-dd-HH-mm</code> |
||||||
|
|
||||||
|
<td>Rollover at the beginning of every minute.</td> |
||||||
|
|
||||||
|
<td>At approximately 11:23,000, on March 9th, 2001, |
||||||
|
<code>/foo/bar.log</code> will be copied to |
||||||
|
<code>/foo/bar.log.2001-03-09-10-22</code>. Logging for the minute |
||||||
|
of 11:23 (9th of March) will be output to |
||||||
|
<code>/foo/bar.log</code> until it is rolled over the next minute. |
||||||
|
|
||||||
|
</table> |
||||||
|
|
||||||
|
<p>Do not use the colon ":" character in anywhere in the |
||||||
|
<b>DatePattern</b> option. The text before the colon is interpeted |
||||||
|
as the protocol specificaion of a URL which is probably not what |
||||||
|
you want. |
||||||
|
|
||||||
|
|
||||||
|
@author Eirik Lygre |
||||||
|
@author Ceki Gülcü*/ |
||||||
|
public class DailyRollingFileAppender extends FileAppender { |
||||||
|
|
||||||
|
|
||||||
|
// The code assumes that the following constants are in a increasing
|
||||||
|
// sequence.
|
||||||
|
static final int TOP_OF_TROUBLE=-1; |
||||||
|
static final int TOP_OF_MINUTE = 0; |
||||||
|
static final int TOP_OF_HOUR = 1; |
||||||
|
static final int HALF_DAY = 2; |
||||||
|
static final int TOP_OF_DAY = 3; |
||||||
|
static final int TOP_OF_WEEK = 4; |
||||||
|
static final int TOP_OF_MONTH = 5; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
The date pattern. By default, the pattern is set to |
||||||
|
"'.'yyyy-MM-dd" meaning daily rollover. |
||||||
|
*/ |
||||||
|
private String datePattern = "'.'yyyy-MM-dd"; |
||||||
|
|
||||||
|
private static final String COMPRESS_SUFFIX = ".gz"; |
||||||
|
|
||||||
|
/** |
||||||
|
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(); |
||||||
|
|
||||||
|
int checkPeriod = TOP_OF_TROUBLE; |
||||||
|
|
||||||
|
// The gmtTimeZone is used only in computeCheckPeriod() method.
|
||||||
|
static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT"); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
The default constructor does nothing. */ |
||||||
|
public DailyRollingFileAppender() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
Instantiate a <code>DailyRollingFileAppender</code> and open the |
||||||
|
file designated by <code>filename</code>. The opened filename will |
||||||
|
become the ouput destination for this appender. |
||||||
|
|
||||||
|
*/ |
||||||
|
public DailyRollingFileAppender (Layout layout, String filename, |
||||||
|
String datePattern) throws IOException { |
||||||
|
super(layout, filename, true); |
||||||
|
this.datePattern = datePattern; |
||||||
|
activateOptions(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
The <b>DatePattern</b> takes a string in the same format as |
||||||
|
expected by {@link SimpleDateFormat}. This options determines the |
||||||
|
rollover schedule. |
||||||
|
*/ |
||||||
|
public void setDatePattern(String pattern) { |
||||||
|
datePattern = pattern; |
||||||
|
} |
||||||
|
|
||||||
|
/** Returns the value of the <b>DatePattern</b> option. */ |
||||||
|
public String getDatePattern() { |
||||||
|
return datePattern; |
||||||
|
} |
||||||
|
|
||||||
|
public void activateOptions() { |
||||||
|
super.activateOptions(); |
||||||
|
if(datePattern != null && fileName != null) { |
||||||
|
now.setTime(System.currentTimeMillis()); |
||||||
|
sdf = new SimpleDateFormat(datePattern); |
||||||
|
int type = computeCheckPeriod(); |
||||||
|
printPeriodicity(type); |
||||||
|
rc.setType(type); |
||||||
|
File file = new File(fileName); |
||||||
|
scheduledFilename = fileName+sdf.format(new Date(file.lastModified()))+COMPRESS_SUFFIX; |
||||||
|
|
||||||
|
} else { |
||||||
|
LogLog.error("Either File or DatePattern options are not set for appender [" |
||||||
|
+name+"]."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void printPeriodicity(int type) { |
||||||
|
switch(type) { |
||||||
|
case TOP_OF_MINUTE: |
||||||
|
LogLog.debug("Appender ["+name+"] to be rolled every minute."); |
||||||
|
break; |
||||||
|
case TOP_OF_HOUR: |
||||||
|
LogLog.debug("Appender ["+name |
||||||
|
+"] to be rolled on top of every hour."); |
||||||
|
break; |
||||||
|
case HALF_DAY: |
||||||
|
LogLog.debug("Appender ["+name |
||||||
|
+"] to be rolled at midday and midnight."); |
||||||
|
break; |
||||||
|
case TOP_OF_DAY: |
||||||
|
LogLog.debug("Appender ["+name |
||||||
|
+"] to be rolled at midnight."); |
||||||
|
break; |
||||||
|
case TOP_OF_WEEK: |
||||||
|
LogLog.debug("Appender ["+name |
||||||
|
+"] to be rolled at start of week."); |
||||||
|
break; |
||||||
|
case TOP_OF_MONTH: |
||||||
|
LogLog.debug("Appender ["+name |
||||||
|
+"] to be rolled at start of every month."); |
||||||
|
break; |
||||||
|
default: |
||||||
|
LogLog.warn("Unknown periodicity 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).
|
||||||
|
|
||||||
|
int computeCheckPeriod() { |
||||||
|
RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault()); |
||||||
|
// set sate to 1970-01-01 00:00:00 GMT
|
||||||
|
Date epoch = new Date(0); |
||||||
|
if(datePattern != null) { |
||||||
|
for(int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) { |
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern); |
||||||
|
simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in GMT
|
||||||
|
String r0 = simpleDateFormat.format(epoch); |
||||||
|
rollingCalendar.setType(i); |
||||||
|
Date next = new Date(rollingCalendar.getNextCheckMillis(epoch)); |
||||||
|
String r1 = simpleDateFormat.format(next); |
||||||
|
//System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
|
||||||
|
if(r0 != null && r1 != null && !r0.equals(r1)) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return TOP_OF_TROUBLE; // Deliberately head for trouble...
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Rollover the current file to a new file. |
||||||
|
*/ |
||||||
|
void rollOver() throws IOException { |
||||||
|
|
||||||
|
/* Compute filename, but only if datePattern is specified */ |
||||||
|
if (datePattern == null) { |
||||||
|
errorHandler.error("Missing DatePattern option in rollOver()."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
String datedFilename = fileName + sdf.format(now) + COMPRESS_SUFFIX; |
||||||
|
// 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 compress it to datedFilename
|
||||||
|
this.closeFile(); |
||||||
|
|
||||||
|
File target = new File(scheduledFilename); |
||||||
|
if (target.exists()) { |
||||||
|
target.delete(); |
||||||
|
} |
||||||
|
|
||||||
|
File file = new File(fileName); |
||||||
|
boolean isGzipSuccess = false; |
||||||
|
|
||||||
|
try (FileInputStream fis = new FileInputStream(file); |
||||||
|
FileOutputStream fos = new FileOutputStream(target); |
||||||
|
GZIPOutputStream gzos = new GZIPOutputStream(fos);) { |
||||||
|
byte[] inbuf = new byte[8102]; |
||||||
|
int n; |
||||||
|
|
||||||
|
while ((n = fis.read(inbuf)) != -1) { |
||||||
|
gzos.write(inbuf, 0, n); |
||||||
|
} |
||||||
|
isGzipSuccess = true; |
||||||
|
} catch (Exception e) { |
||||||
|
LogLog.error("Compress " + fileName + " to " + scheduledFilename + " failed."); |
||||||
|
LogLog.error(e.getMessage(), e); |
||||||
|
} |
||||||
|
|
||||||
|
boolean isDeleteSuccess = true; |
||||||
|
if (isGzipSuccess) { |
||||||
|
isDeleteSuccess = file.delete(); |
||||||
|
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."); |
||||||
|
} |
||||||
|
if(!isDeleteSuccess){ |
||||||
|
synchronized (this) { |
||||||
|
if (scheduledFilename.equals(datedFilename)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
LogLog.debug("file delete failed, empty it."); |
||||||
|
emptyFile(file); |
||||||
|
scheduledFilename = datedFilename; |
||||||
|
} |
||||||
|
}else { |
||||||
|
scheduledFilename = datedFilename; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @param file empty file |
||||||
|
*/ |
||||||
|
private static void emptyFile(File file) { |
||||||
|
try(FileWriter fileWriter = new FileWriter(file)) { |
||||||
|
if (!file.exists()) { |
||||||
|
file.createNewFile(); |
||||||
|
} |
||||||
|
fileWriter.write(""); |
||||||
|
fileWriter.flush(); |
||||||
|
} catch (IOException e) { |
||||||
|
LogLog.debug("empty file failed:" + e.getMessage() + e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method differentiates DailyRollingFileAppender 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. |
||||||
|
* */ |
||||||
|
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 DailyRollingFileAppender. |
||||||
|
* 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; |
||||||
|
|
||||||
|
int type = DailyRollingFileAppender.TOP_OF_TROUBLE; |
||||||
|
|
||||||
|
RollingCalendar() { |
||||||
|
super(); |
||||||
|
} |
||||||
|
|
||||||
|
RollingCalendar(TimeZone tz, Locale locale) { |
||||||
|
super(tz, locale); |
||||||
|
} |
||||||
|
|
||||||
|
void setType(int type) { |
||||||
|
this.type = type; |
||||||
|
} |
||||||
|
|
||||||
|
public long getNextCheckMillis(Date now) { |
||||||
|
return getNextCheckDate(now).getTime(); |
||||||
|
} |
||||||
|
|
||||||
|
public Date getNextCheckDate(Date now) { |
||||||
|
this.setTime(now); |
||||||
|
|
||||||
|
switch(type) { |
||||||
|
case DailyRollingFileAppender.TOP_OF_MINUTE: |
||||||
|
this.set(Calendar.SECOND, 0); |
||||||
|
this.set(Calendar.MILLISECOND, 0); |
||||||
|
this.add(Calendar.MINUTE, 1); |
||||||
|
break; |
||||||
|
case DailyRollingFileAppender.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; |
||||||
|
case DailyRollingFileAppender.HALF_DAY: |
||||||
|
this.set(Calendar.MINUTE, 0); |
||||||
|
this.set(Calendar.SECOND, 0); |
||||||
|
this.set(Calendar.MILLISECOND, 0); |
||||||
|
int hour = 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; |
||||||
|
case DailyRollingFileAppender.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; |
||||||
|
case DailyRollingFileAppender.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; |
||||||
|
case DailyRollingFileAppender.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: |
||||||
|
throw new IllegalStateException("Unknown periodicity type."); |
||||||
|
} |
||||||
|
return getTime(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue