diff --git a/fine-log4j/src/main/java/com/fr/third/apache/log4j/DailyRollingFileAppender.java b/fine-log4j/src/main/java/com/fr/third/apache/log4j/DailyRollingFileAppender.java new file mode 100644 index 000000000..7b4088fa0 --- /dev/null +++ b/fine-log4j/src/main/java/com/fr/third/apache/log4j/DailyRollingFileAppender.java @@ -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. + +
The rolling schedule is specified by the DatePattern + option. This pattern should follow the {@link SimpleDateFormat} + conventions. In particular, you must 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. + +
For example, if the File option is set to
+ /foo/bar.log
and the DatePattern set to
+ '.'yyyy-MM-dd
, on 2001-02-16 at midnight, the logging
+ file /foo/bar.log
will be copied to
+ /foo/bar.log.2001-02-16
and logging for 2001-02-17
+ will continue in /foo/bar.log
until it rolls over
+ the next day.
+
+
Is is possible to specify monthly, weekly, half-daily, daily, + hourly, or minutely rollover schedules. + +
DatePattern | +Rollover schedule | +Example | + +
---|---|---|
'.'yyyy-MM
+ | Rollover at the beginning of each month | + +At midnight of May 31st, 2002 /foo/bar.log will be
+ copied to /foo/bar.log.2002-05 . Logging for the month
+ of June will be output to /foo/bar.log until it is
+ also rolled over the next month.
+
+ |
'.'yyyy-ww
+
+ | Rollover at the first day of each week. The first day of the + week depends on the locale. | + +Assuming the first day of the week is Sunday, on Saturday
+ midnight, June 9th 2002, the file /foo/bar.log will be
+ copied to /foo/bar.log.2002-23. Logging for the 24th week
+ of 2002 will be output to /foo/bar.log until it is
+ rolled over the next week.
+
+ |
'.'yyyy-MM-dd
+
+ | Rollover at midnight each day. | + +At midnight, on March 8th, 2002, /foo/bar.log will
+ be copied to /foo/bar.log.2002-03-08 . Logging for the
+ 9th day of March will be output to /foo/bar.log until
+ it is rolled over the next day.
+
+ |
'.'yyyy-MM-dd-a
+
+ | Rollover at midnight and midday of each day. | + +At noon, on March 9th, 2002, /foo/bar.log will be
+ copied to /foo/bar.log.2002-03-09-AM . Logging for the
+ afternoon of the 9th will be output to /foo/bar.log
+ until it is rolled over at midnight.
+
+ |
'.'yyyy-MM-dd-HH
+
+ | Rollover at the top of every hour. | + +At approximately 11:00.000 o'clock on March 9th, 2002,
+ /foo/bar.log will be copied to
+ /foo/bar.log.2002-03-09-10 . Logging for the 11th hour
+ of the 9th of March will be output to /foo/bar.log
+ until it is rolled over at the beginning of the next hour.
+
+
+ |
'.'yyyy-MM-dd-HH-mm
+
+ | Rollover at the beginning of every minute. | + +At approximately 11:23,000, on March 9th, 2001,
+ /foo/bar.log will be copied to
+ /foo/bar.log.2001-03-09-10-22 . Logging for the minute
+ of 11:23 (9th of March) will be output to
+ /foo/bar.log until it is rolled over the next minute.
+
+ |
Do not use the colon ":" character in anywhere in the
+ DatePattern 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 DailyRollingFileAppender
and open the
+ file designated by filename
. 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 DatePattern 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 DatePattern 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.
+ *
+ *
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(); + } +}