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