View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.appender.rolling;
    18
    19  import java.text.SimpleDateFormat;
    20  import java.util.ArrayList;
    21  import java.util.Calendar;
    22  import java.util.Date;
    23  import java.util.List;
    24
    25  import org.apache.logging.log4j.Logger;
    26  import org.apache.logging.log4j.core.LogEvent;
    27  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
    28  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
    29  import org.apache.logging.log4j.core.pattern.ArrayPatternConverter;
    30  import org.apache.logging.log4j.core.pattern.DatePatternConverter;
    31  import org.apache.logging.log4j.core.pattern.FormattingInfo;
    32  import org.apache.logging.log4j.core.pattern.PatternConverter;
    33  import org.apache.logging.log4j.core.pattern.PatternParser;
    34  import org.apache.logging.log4j.status.StatusLogger;
    35
    36  /**
    37   * Parse the rollover pattern.
    38   */
    39  public class PatternProcessor {
    40
    41      protected static final Logger LOGGER = StatusLogger.getLogger();
    42      private static final String KEY = "FileConverter";
    43
    44      private static final char YEAR_CHAR = 'y';
    45      private static final char MONTH_CHAR = 'M';
    46      private static final char[] WEEK_CHARS = {'w', 'W'};
    47      private static final char[] DAY_CHARS = {'D', 'd', 'F', 'E'};
    48      private static final char[] HOUR_CHARS = {'H', 'K', 'h', 'k'};
    49      private static final char MINUTE_CHAR = 'm';
    50      private static final char SECOND_CHAR = 's';
    51      private static final char MILLIS_CHAR = 'S';
    52
    53      private final ArrayPatternConverter[] patternConverters;
    54      private final FormattingInfo[] patternFields;
    55
    56      private long prevFileTime = 0;
    57      private long nextFileTime = 0;
    58
    59      private RolloverFrequency frequency = null;
    60
    61      /**
    62       * Constructor.
    63       * @param pattern The file pattern.
    64       */
    65      public PatternProcessor(final String pattern) {
    66          final PatternParser parser = createPatternParser();
    67          final List<PatternConverter> converters = new ArrayList<PatternConverter>();
    68          final List<FormattingInfo> fields = new ArrayList<FormattingInfo>();
    69          parser.parse(pattern, converters, fields, false, false);
    70          final FormattingInfo[] infoArray = new FormattingInfo[fields.size()];
    71          patternFields = fields.toArray(infoArray);
    72          final ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()];
    73          patternConverters = converters.toArray(converterArray);
    74
    75          for (final ArrayPatternConverter converter : patternConverters) {
    76              if (converter instanceof DatePatternConverter) {
    77                  final DatePatternConverter dateConverter = (DatePatternConverter) converter;
    78                  frequency = calculateFrequency(dateConverter.getPattern());
    79              }
    80          }
    81      }
    82
    83      /**
    84       * Returns the next potential rollover time.
    85       * @param current The current time.
    86       * @param increment The increment to the next time.
    87       * @param modulus If true the time will be rounded to occur on a boundary aligned with the increment.
    88       * @return the next potential rollover time and the timestamp for the target file.
    89       */
    90      public long getNextTime(final long current, final int increment, final boolean modulus) {
    91          prevFileTime = nextFileTime;
    92          long nextTime;
    93
    94          if (frequency == null) {
    95              throw new IllegalStateException("Pattern does not contain a date");
    96          }
    97          final Calendar currentCal = Calendar.getInstance();
    98          currentCal.setTimeInMillis(current);
    99          final Calendar cal = Calendar.getInstance();
    100         cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
    101         cal.set(Calendar.MILLISECOND, 0);
    102         if (frequency == RolloverFrequency.ANNUALLY) {
    103             increment(cal, Calendar.YEAR, increment, modulus);
    104             nextTime = cal.getTimeInMillis();
    105             cal.add(Calendar.YEAR, -1);
    106             nextFileTime = cal.getTimeInMillis();
    107             return debugGetNextTime(nextTime);
    108         }
    109         cal.set(Calendar.MONTH, currentCal.get(Calendar.MONTH));
    110         if (frequency == RolloverFrequency.MONTHLY) {
    111             increment(cal, Calendar.MONTH, increment, modulus);
    112             nextTime = cal.getTimeInMillis();
    113             cal.add(Calendar.MONTH, -1);
    114             nextFileTime = cal.getTimeInMillis();
    115             return debugGetNextTime(nextTime);
    116         }
    117         if (frequency == RolloverFrequency.WEEKLY) {
    118             cal.set(Calendar.WEEK_OF_YEAR, currentCal.get(Calendar.WEEK_OF_YEAR));
    119             increment(cal, Calendar.WEEK_OF_YEAR, increment, modulus);
    120             cal.set(Calendar.DAY_OF_WEEK, currentCal.getFirstDayOfWeek());
    121             nextTime = cal.getTimeInMillis();
    122             cal.add(Calendar.WEEK_OF_YEAR, -1);
    123             nextFileTime = cal.getTimeInMillis();
    124             return debugGetNextTime(nextTime);
    125         }
    126         cal.set(Calendar.DAY_OF_YEAR, currentCal.get(Calendar.DAY_OF_YEAR));
    127         if (frequency == RolloverFrequency.DAILY) {
    128             increment(cal, Calendar.DAY_OF_YEAR, increment, modulus);
    129             nextTime = cal.getTimeInMillis();
    130             cal.add(Calendar.DAY_OF_YEAR, -1);
    131             nextFileTime = cal.getTimeInMillis();
    132             return debugGetNextTime(nextTime);
    133         }
    134         cal.set(Calendar.HOUR_OF_DAY, currentCal.get(Calendar.HOUR_OF_DAY));
    135         if (frequency == RolloverFrequency.HOURLY) {
    136             increment(cal, Calendar.HOUR_OF_DAY, increment, modulus);
    137             nextTime = cal.getTimeInMillis();
    138             cal.add(Calendar.HOUR_OF_DAY, -1);
    139             nextFileTime = cal.getTimeInMillis();
    140             return debugGetNextTime(nextTime);
    141         }
    142         cal.set(Calendar.MINUTE, currentCal.get(Calendar.MINUTE));
    143         if (frequency == RolloverFrequency.EVERY_MINUTE) {
    144             increment(cal, Calendar.MINUTE, increment, modulus);
    145             nextTime = cal.getTimeInMillis();
    146             cal.add(Calendar.MINUTE, -1);
    147             nextFileTime = cal.getTimeInMillis();
    148             return debugGetNextTime(nextTime);
    149         }
    150         cal.set(Calendar.SECOND, currentCal.get(Calendar.SECOND));
    151         if (frequency == RolloverFrequency.EVERY_SECOND) {
    152             increment(cal, Calendar.SECOND, increment, modulus);
    153             nextTime = cal.getTimeInMillis();
    154             cal.add(Calendar.SECOND, -1);
    155             nextFileTime = cal.getTimeInMillis();
    156             return debugGetNextTime(nextTime);
    157         }
    158         cal.set(Calendar.MILLISECOND, currentCal.get(Calendar.MILLISECOND));
    159         increment(cal, Calendar.MILLISECOND, increment, modulus);
    160         nextTime = cal.getTimeInMillis();
    161         cal.add(Calendar.MILLISECOND, -1);
    162         nextFileTime = cal.getTimeInMillis();
    163         return debugGetNextTime(nextTime);
    164     }
    165
    166     public void updateTime() {
    167         prevFileTime = nextFileTime;
    168     }
    169
    170     private long debugGetNextTime(final long nextTime) {
    171         if (LOGGER.isTraceEnabled()) {
    172             LOGGER.trace("PatternProcessor.getNextTime returning {}, nextFileTime={}, prevFileTime={}, current={}, freq={}", //
    173                     format(nextTime), format(nextFileTime), format(prevFileTime), format(System.currentTimeMillis()), frequency);
    174         }
    175         return nextTime;
    176     }
    177
    178     private String format(final long time) {
    179         return new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss.SSS").format(new Date(time));
    180     }
    181
    182     private void increment(final Calendar cal, final int type, final int increment, final boolean modulate) {
    183         final int interval =  modulate ? increment - (cal.get(type) % increment) : increment;
    184         cal.add(type, interval);
    185     }
    186
    187     /**
    188      * Format file name.
    189      * @param buf string buffer to which formatted file name is appended, may not be null.
    190      * @param obj object to be evaluated in formatting, may not be null.
    191      */
    192     public final void formatFileName(final StringBuilder buf, final Object obj) {
    193         final long time = prevFileTime == 0 ? System.currentTimeMillis() : prevFileTime;
    194         formatFileName(buf, new Date(time), obj);
    195     }
    196
    197     /**
    198      * Format file name.
    199      * @param subst The StrSubstitutor.
    200      * @param buf string buffer to which formatted file name is appended, may not be null.
    201      * @param obj object to be evaluated in formatting, may not be null.
    202      */
    203     public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final Object obj) {
    204         // LOG4J2-628: we deliberately use System time, not the log4j.Clock time
    205         // for creating the file name of rolled-over files.
    206         final long time = prevFileTime == 0 ? System.currentTimeMillis() : prevFileTime;
    207         formatFileName(buf, new Date(time), obj);
    208         final LogEvent event = new Log4jLogEvent(time);
    209         final String fileName = subst.replace(event, buf);
    210         buf.setLength(0);
    211         buf.append(fileName);
    212     }
    213
    214     /**
    215      * Format file name.
    216      * @param buf string buffer to which formatted file name is appended, may not be null.
    217      * @param objects objects to be evaluated in formatting, may not be null.
    218      */
    219     protected final void formatFileName(final StringBuilder buf, final Object... objects) {
    220         for (int i = 0; i < patternConverters.length; i++) {
    221             final int fieldStart = buf.length();
    222             patternConverters[i].format(buf, objects);
    223
    224             if (patternFields[i] != null) {
    225                 patternFields[i].format(fieldStart, buf);
    226             }
    227         }
    228     }
    229
    230     private RolloverFrequency calculateFrequency(final String pattern) {
    231         if (patternContains(pattern, MILLIS_CHAR)) {
    232             return RolloverFrequency.EVERY_MILLISECOND;
    233         }
    234         if (patternContains(pattern, SECOND_CHAR)) {
    235             return RolloverFrequency.EVERY_SECOND;
    236         }
    237         if (patternContains(pattern, MINUTE_CHAR)) {
    238             return RolloverFrequency.EVERY_MINUTE;
    239         }
    240         if (patternContains(pattern, HOUR_CHARS)) {
    241             return RolloverFrequency.HOURLY;
    242         }
    243         if (patternContains(pattern, DAY_CHARS)) {
    244             return RolloverFrequency.DAILY;
    245         }
    246         if (patternContains(pattern, WEEK_CHARS)) {
    247             return RolloverFrequency.WEEKLY;
    248         }
    249         if (patternContains(pattern, MONTH_CHAR)) {
    250             return RolloverFrequency.MONTHLY;
    251         }
    252         if (patternContains(pattern, YEAR_CHAR)) {
    253             return RolloverFrequency.ANNUALLY;
    254         }
    255         return null;
    256     }
    257
    258     private PatternParser createPatternParser() {
    259
    260         return new PatternParser(null, KEY, null);
    261     }
    262
    263     private boolean patternContains(final String pattern, final char... chars) {
    264         for (final char character : chars) {
    265             if (patternContains(pattern, character)) {
    266                 return true;
    267             }
    268         }
    269         return false;
    270     }
    271
    272     private boolean patternContains(final String pattern, final char character) {
    273         return pattern.indexOf(character) >= 0;
    274     }
    275 }