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