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 }