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