001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core.appender.rolling;
018
019 import java.text.SimpleDateFormat;
020 import java.util.ArrayList;
021 import java.util.Calendar;
022 import java.util.Date;
023 import java.util.List;
024
025 import org.apache.logging.log4j.Logger;
026 import org.apache.logging.log4j.core.LogEvent;
027 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
028 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
029 import org.apache.logging.log4j.core.pattern.ArrayPatternConverter;
030 import org.apache.logging.log4j.core.pattern.DatePatternConverter;
031 import org.apache.logging.log4j.core.pattern.FormattingInfo;
032 import org.apache.logging.log4j.core.pattern.PatternConverter;
033 import org.apache.logging.log4j.core.pattern.PatternParser;
034 import org.apache.logging.log4j.status.StatusLogger;
035
036 /**
037 * Parse the rollover pattern.
038 */
039 public class PatternProcessor {
040
041 protected static final Logger LOGGER = StatusLogger.getLogger();
042 private static final String KEY = "FileConverter";
043
044 private static final char YEAR_CHAR = 'y';
045 private static final char MONTH_CHAR = 'M';
046 private static final char[] WEEK_CHARS = {'w', 'W'};
047 private static final char[] DAY_CHARS = {'D', 'd', 'F', 'E'};
048 private static final char[] HOUR_CHARS = {'H', 'K', 'h', 'k'};
049 private static final char MINUTE_CHAR = 'm';
050 private static final char SECOND_CHAR = 's';
051 private static final char MILLIS_CHAR = 'S';
052
053 private final ArrayPatternConverter[] patternConverters;
054 private final FormattingInfo[] patternFields;
055
056 private long prevFileTime = 0;
057 private long nextFileTime = 0;
058
059 private RolloverFrequency frequency = null;
060
061 /**
062 * Constructor.
063 * @param pattern The file pattern.
064 */
065 public PatternProcessor(final String pattern) {
066 final PatternParser parser = createPatternParser();
067 final List<PatternConverter> converters = new ArrayList<PatternConverter>();
068 final List<FormattingInfo> fields = new ArrayList<FormattingInfo>();
069 parser.parse(pattern, converters, fields, false, false);
070 final FormattingInfo[] infoArray = new FormattingInfo[fields.size()];
071 patternFields = fields.toArray(infoArray);
072 final ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()];
073 patternConverters = converters.toArray(converterArray);
074
075 for (final ArrayPatternConverter converter : patternConverters) {
076 if (converter instanceof DatePatternConverter) {
077 final DatePatternConverter dateConverter = (DatePatternConverter) converter;
078 frequency = calculateFrequency(dateConverter.getPattern());
079 }
080 }
081 }
082
083 /**
084 * Returns the next potential rollover time.
085 * @param current The current time.
086 * @param increment The increment to the next time.
087 * @param modulus If true the time will be rounded to occur on a boundary aligned with the increment.
088 * @return the next potential rollover time and the timestamp for the target file.
089 */
090 public long getNextTime(final long current, final int increment, final boolean modulus) {
091 prevFileTime = nextFileTime;
092 long nextTime;
093
094 if (frequency == null) {
095 throw new IllegalStateException("Pattern does not contain a date");
096 }
097 final Calendar currentCal = Calendar.getInstance();
098 currentCal.setTimeInMillis(current);
099 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 public void updateTime() {
167 prevFileTime = nextFileTime;
168 }
169
170 private long debugGetNextTime(final long nextTime) {
171 if (LOGGER.isTraceEnabled()) {
172 LOGGER.trace("PatternProcessor.getNextTime returning {}, nextFileTime={}, prevFileTime={}, current={}, freq={}", //
173 format(nextTime), format(nextFileTime), format(prevFileTime), format(System.currentTimeMillis()), frequency);
174 }
175 return nextTime;
176 }
177
178 private String format(final long time) {
179 return new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss.SSS").format(new Date(time));
180 }
181
182 private void increment(final Calendar cal, final int type, final int increment, final boolean modulate) {
183 final int interval = modulate ? increment - (cal.get(type) % increment) : increment;
184 cal.add(type, interval);
185 }
186
187 /**
188 * Format file name.
189 * @param buf string buffer to which formatted file name is appended, may not be null.
190 * @param obj object to be evaluated in formatting, may not be null.
191 */
192 public final void formatFileName(final StringBuilder buf, final Object obj) {
193 final long time = prevFileTime == 0 ? System.currentTimeMillis() : prevFileTime;
194 formatFileName(buf, new Date(time), obj);
195 }
196
197 /**
198 * Format file name.
199 * @param subst The StrSubstitutor.
200 * @param buf string buffer to which formatted file name is appended, may not be null.
201 * @param obj object to be evaluated in formatting, may not be null.
202 */
203 public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final Object obj) {
204 // LOG4J2-628: we deliberately use System time, not the log4j.Clock time
205 // for creating the file name of rolled-over files.
206 final long time = prevFileTime == 0 ? System.currentTimeMillis() : prevFileTime;
207 formatFileName(buf, new Date(time), obj);
208 final LogEvent event = new Log4jLogEvent(time);
209 final String fileName = subst.replace(event, buf);
210 buf.setLength(0);
211 buf.append(fileName);
212 }
213
214 /**
215 * Format file name.
216 * @param buf string buffer to which formatted file name is appended, may not be null.
217 * @param objects objects to be evaluated in formatting, may not be null.
218 */
219 protected final void formatFileName(final StringBuilder buf, final Object... objects) {
220 for (int i = 0; i < patternConverters.length; i++) {
221 final int fieldStart = buf.length();
222 patternConverters[i].format(buf, objects);
223
224 if (patternFields[i] != null) {
225 patternFields[i].format(fieldStart, buf);
226 }
227 }
228 }
229
230 private RolloverFrequency calculateFrequency(final String pattern) {
231 if (patternContains(pattern, MILLIS_CHAR)) {
232 return RolloverFrequency.EVERY_MILLISECOND;
233 }
234 if (patternContains(pattern, SECOND_CHAR)) {
235 return RolloverFrequency.EVERY_SECOND;
236 }
237 if (patternContains(pattern, MINUTE_CHAR)) {
238 return RolloverFrequency.EVERY_MINUTE;
239 }
240 if (patternContains(pattern, HOUR_CHARS)) {
241 return RolloverFrequency.HOURLY;
242 }
243 if (patternContains(pattern, DAY_CHARS)) {
244 return RolloverFrequency.DAILY;
245 }
246 if (patternContains(pattern, WEEK_CHARS)) {
247 return RolloverFrequency.WEEKLY;
248 }
249 if (patternContains(pattern, MONTH_CHAR)) {
250 return RolloverFrequency.MONTHLY;
251 }
252 if (patternContains(pattern, YEAR_CHAR)) {
253 return RolloverFrequency.ANNUALLY;
254 }
255 return null;
256 }
257
258 private PatternParser createPatternParser() {
259
260 return new PatternParser(null, KEY, null);
261 }
262
263 private boolean patternContains(final String pattern, final char... chars) {
264 for (final char character : chars) {
265 if (patternContains(pattern, character)) {
266 return true;
267 }
268 }
269 return false;
270 }
271
272 private boolean patternContains(final String pattern, final char character) {
273 return pattern.indexOf(character) >= 0;
274 }
275 }