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