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