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   * Parses 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      private long currentFileTime = 0;
59  
60      private boolean isTimeBased = false;
61  
62      private RolloverFrequency frequency = null;
63  
64      private final String pattern;
65  
66      public String getPattern() {
67          return pattern;
68      }
69  
70      @Override
71      public String toString() {
72          return pattern;
73      }
74  
75      /**
76       * Constructor.
77       * @param pattern The file pattern.
78       */
79      public PatternProcessor(final String pattern) {
80          this.pattern = pattern;
81          final PatternParser parser = createPatternParser();
82          final List<PatternConverter> converters = new ArrayList<>();
83          final List<FormattingInfo> fields = new ArrayList<>();
84          parser.parse(pattern, converters, fields, false, false, false);
85          final FormattingInfo[] infoArray = new FormattingInfo[fields.size()];
86          patternFields = fields.toArray(infoArray);
87          final ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()];
88          patternConverters = converters.toArray(converterArray);
89  
90          for (final ArrayPatternConverter converter : patternConverters) {
91              if (converter instanceof DatePatternConverter) {
92                  final DatePatternConverter dateConverter = (DatePatternConverter) converter;
93                  frequency = calculateFrequency(dateConverter.getPattern());
94              }
95          }
96      }
97  
98      /**
99       * Copy constructor with another pattern as source.
100      *
101      * @param pattern  The file pattern.
102      * @param copy Source pattern processor
103      */
104     public PatternProcessor(final String pattern, final PatternProcessor copy) {
105         this(pattern);
106         this.prevFileTime = copy.prevFileTime;
107         this.nextFileTime = copy.nextFileTime;
108         this.currentFileTime = copy.currentFileTime;
109     }
110 
111     public void setTimeBased(boolean isTimeBased) {
112         this.isTimeBased = isTimeBased;
113     }
114 
115     public long getCurrentFileTime() {
116         return currentFileTime;
117     }
118 
119     public void setCurrentFileTime(final long currentFileTime) {
120         this.currentFileTime = currentFileTime;
121     }
122 
123     public long getPrevFileTime() {
124         return prevFileTime;
125     }
126 
127     public void setPrevFileTime(final long prevFileTime) {
128         LOGGER.debug("Setting prev file time to {}", new Date(prevFileTime));
129         this.prevFileTime = prevFileTime;
130     }
131 
132     /**
133      * Returns the next potential rollover time.
134      * @param currentMillis The current time.
135      * @param increment The increment to the next time.
136      * @param modulus If true the time will be rounded to occur on a boundary aligned with the increment.
137      * @return the next potential rollover time and the timestamp for the target file.
138      */
139     public long getNextTime(final long currentMillis, final int increment, final boolean modulus) {
140         //
141         // https://issues.apache.org/jira/browse/LOG4J2-1232
142         // Call setMinimalDaysInFirstWeek(7);
143         //
144         prevFileTime = nextFileTime;
145         long nextTime;
146 
147         if (frequency == null) {
148             throw new IllegalStateException("Pattern does not contain a date");
149         }
150         final Calendar currentCal = Calendar.getInstance();
151         currentCal.setTimeInMillis(currentMillis);
152         final Calendar cal = Calendar.getInstance();
153         currentCal.setMinimalDaysInFirstWeek(7);
154         cal.setMinimalDaysInFirstWeek(7);
155         cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
156         cal.set(Calendar.MILLISECOND, 0);
157         if (frequency == RolloverFrequency.ANNUALLY) {
158             increment(cal, Calendar.YEAR, increment, modulus);
159             nextTime = cal.getTimeInMillis();
160             cal.add(Calendar.YEAR, -1);
161             nextFileTime = cal.getTimeInMillis();
162             return debugGetNextTime(nextTime);
163         }
164         cal.set(Calendar.MONTH, currentCal.get(Calendar.MONTH));
165         if (frequency == RolloverFrequency.MONTHLY) {
166             increment(cal, Calendar.MONTH, increment, modulus);
167             nextTime = cal.getTimeInMillis();
168             cal.add(Calendar.MONTH, -1);
169             nextFileTime = cal.getTimeInMillis();
170             return debugGetNextTime(nextTime);
171         }
172         if (frequency == RolloverFrequency.WEEKLY) {
173             cal.set(Calendar.WEEK_OF_YEAR, currentCal.get(Calendar.WEEK_OF_YEAR));
174             increment(cal, Calendar.WEEK_OF_YEAR, increment, modulus);
175             cal.set(Calendar.DAY_OF_WEEK, currentCal.getFirstDayOfWeek());
176             nextTime = cal.getTimeInMillis();
177             cal.add(Calendar.WEEK_OF_YEAR, -1);
178             nextFileTime = cal.getTimeInMillis();
179             return debugGetNextTime(nextTime);
180         }
181         cal.set(Calendar.DAY_OF_YEAR, currentCal.get(Calendar.DAY_OF_YEAR));
182         if (frequency == RolloverFrequency.DAILY) {
183             increment(cal, Calendar.DAY_OF_YEAR, increment, modulus);
184             nextTime = cal.getTimeInMillis();
185             cal.add(Calendar.DAY_OF_YEAR, -1);
186             nextFileTime = cal.getTimeInMillis();
187             return debugGetNextTime(nextTime);
188         }
189         cal.set(Calendar.HOUR_OF_DAY, currentCal.get(Calendar.HOUR_OF_DAY));
190         if (frequency == RolloverFrequency.HOURLY) {
191             increment(cal, Calendar.HOUR_OF_DAY, increment, modulus);
192             nextTime = cal.getTimeInMillis();
193             cal.add(Calendar.HOUR_OF_DAY, -1);
194             nextFileTime = cal.getTimeInMillis();
195             return debugGetNextTime(nextTime);
196         }
197         cal.set(Calendar.MINUTE, currentCal.get(Calendar.MINUTE));
198         if (frequency == RolloverFrequency.EVERY_MINUTE) {
199             increment(cal, Calendar.MINUTE, increment, modulus);
200             nextTime = cal.getTimeInMillis();
201             cal.add(Calendar.MINUTE, -1);
202             nextFileTime = cal.getTimeInMillis();
203             return debugGetNextTime(nextTime);
204         }
205         cal.set(Calendar.SECOND, currentCal.get(Calendar.SECOND));
206         if (frequency == RolloverFrequency.EVERY_SECOND) {
207             increment(cal, Calendar.SECOND, increment, modulus);
208             nextTime = cal.getTimeInMillis();
209             cal.add(Calendar.SECOND, -1);
210             nextFileTime = cal.getTimeInMillis();
211             return debugGetNextTime(nextTime);
212         }
213         cal.set(Calendar.MILLISECOND, currentCal.get(Calendar.MILLISECOND));
214         increment(cal, Calendar.MILLISECOND, increment, modulus);
215         nextTime = cal.getTimeInMillis();
216         cal.add(Calendar.MILLISECOND, -1);
217         nextFileTime = cal.getTimeInMillis();
218         return debugGetNextTime(nextTime);
219     }
220 
221     public void updateTime() {
222     	if (nextFileTime != 0 || !isTimeBased) {
223 			prevFileTime = nextFileTime;
224 		}
225     }
226 
227     private long debugGetNextTime(final long nextTime) {
228         if (LOGGER.isTraceEnabled()) {
229             LOGGER.trace("PatternProcessor.getNextTime returning {}, nextFileTime={}, prevFileTime={}, current={}, freq={}", //
230                     format(nextTime), format(nextFileTime), format(prevFileTime), format(System.currentTimeMillis()), frequency);
231         }
232         return nextTime;
233     }
234 
235     private String format(final long time) {
236         return new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss.SSS").format(new Date(time));
237     }
238 
239     private void increment(final Calendar cal, final int type, final int increment, final boolean modulate) {
240         final int interval =  modulate ? increment - (cal.get(type) % increment) : increment;
241         cal.add(type, interval);
242     }
243 
244     /**
245      * Format file name.
246      * @param buf string buffer to which formatted file name is appended, may not be null.
247      * @param obj object to be evaluated in formatting, may not be null.
248      */
249     public final void formatFileName(final StringBuilder buf, final boolean useCurrentTime, final Object obj) {
250         long time = useCurrentTime ? currentFileTime : prevFileTime;
251         if (time == 0) {
252             time = System.currentTimeMillis();
253         }
254         formatFileName(buf, new Date(time), obj);
255     }
256 
257     /**
258      * Formats file name.
259      * @param subst The StrSubstitutor.
260      * @param buf string buffer to which formatted file name is appended, may not be null.
261      * @param obj object to be evaluated in formatting, may not be null.
262      */
263     public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final Object obj) {
264         formatFileName(subst, buf, false, obj);
265     }
266 
267     /**
268      * Formats file name.
269      * @param subst The StrSubstitutor.
270      * @param buf string buffer to which formatted file name is appended, may not be null.
271      * @param obj object to be evaluated in formatting, may not be null.
272      */
273     public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final boolean useCurrentTime,
274                                      final Object obj) {
275         // LOG4J2-628: we deliberately use System time, not the log4j.Clock time
276         // for creating the file name of rolled-over files.
277         LOGGER.debug("Formatting file name. useCurrentTime={}. currentFileTime={}, prevFileTime={}",
278             useCurrentTime, currentFileTime, prevFileTime);
279         final long time = useCurrentTime ? currentFileTime != 0 ? currentFileTime : System.currentTimeMillis() :
280                 prevFileTime != 0 ? prevFileTime : System.currentTimeMillis();
281         formatFileName(buf, new Date(time), obj);
282         final LogEvent event = new Log4jLogEvent.Builder().setTimeMillis(time).build();
283         final String fileName = subst.replace(event, buf);
284         buf.setLength(0);
285         buf.append(fileName);
286     }
287 
288     /**
289      * Formats file name.
290      * @param buf string buffer to which formatted file name is appended, may not be null.
291      * @param objects objects to be evaluated in formatting, may not be null.
292      */
293     protected final void formatFileName(final StringBuilder buf, final Object... objects) {
294         for (int i = 0; i < patternConverters.length; i++) {
295             final int fieldStart = buf.length();
296             patternConverters[i].format(buf, objects);
297 
298             if (patternFields[i] != null) {
299                 patternFields[i].format(fieldStart, buf);
300             }
301         }
302     }
303 
304     private RolloverFrequency calculateFrequency(final String pattern) {
305         if (patternContains(pattern, MILLIS_CHAR)) {
306             return RolloverFrequency.EVERY_MILLISECOND;
307         }
308         if (patternContains(pattern, SECOND_CHAR)) {
309             return RolloverFrequency.EVERY_SECOND;
310         }
311         if (patternContains(pattern, MINUTE_CHAR)) {
312             return RolloverFrequency.EVERY_MINUTE;
313         }
314         if (patternContains(pattern, HOUR_CHARS)) {
315             return RolloverFrequency.HOURLY;
316         }
317         if (patternContains(pattern, DAY_CHARS)) {
318             return RolloverFrequency.DAILY;
319         }
320         if (patternContains(pattern, WEEK_CHARS)) {
321             return RolloverFrequency.WEEKLY;
322         }
323         if (patternContains(pattern, MONTH_CHAR)) {
324             return RolloverFrequency.MONTHLY;
325         }
326         if (patternContains(pattern, YEAR_CHAR)) {
327             return RolloverFrequency.ANNUALLY;
328         }
329         return null;
330     }
331 
332     private PatternParser createPatternParser() {
333 
334         return new PatternParser(null, KEY, null);
335     }
336 
337     private boolean patternContains(final String pattern, final char... chars) {
338         for (final char character : chars) {
339             if (patternContains(pattern, character)) {
340                 return true;
341             }
342         }
343         return false;
344     }
345 
346     private boolean patternContains(final String pattern, final char character) {
347         return pattern.indexOf(character) >= 0;
348     }
349 
350     public RolloverFrequency getFrequency() {
351         return frequency;
352     }
353 
354     public long getNextFileTime() {
355         return nextFileTime;
356     }
357 
358 }