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