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 */ 017package org.apache.logging.log4j.core.appender.rolling; 018 019import java.text.SimpleDateFormat; 020import java.util.ArrayList; 021import java.util.Calendar; 022import java.util.Date; 023import java.util.List; 024 025import org.apache.logging.log4j.Logger; 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.impl.Log4jLogEvent; 028import org.apache.logging.log4j.core.lookup.StrSubstitutor; 029import org.apache.logging.log4j.core.pattern.ArrayPatternConverter; 030import org.apache.logging.log4j.core.pattern.DatePatternConverter; 031import org.apache.logging.log4j.core.pattern.FormattingInfo; 032import org.apache.logging.log4j.core.pattern.PatternConverter; 033import org.apache.logging.log4j.core.pattern.PatternParser; 034import org.apache.logging.log4j.status.StatusLogger; 035 036/** 037 * Parses the rollover pattern. 038 */ 039public 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 private long currentFileTime = 0; 059 060 private boolean isTimeBased = false; 061 062 private RolloverFrequency frequency = null; 063 064 private final String pattern; 065 066 public String getPattern() { 067 return pattern; 068 } 069 070 @Override 071 public String toString() { 072 return pattern; 073 } 074 075 /** 076 * Constructor. 077 * @param pattern The file pattern. 078 */ 079 public PatternProcessor(final String pattern) { 080 this.pattern = pattern; 081 final PatternParser parser = createPatternParser(); 082 final List<PatternConverter> converters = new ArrayList<>(); 083 final List<FormattingInfo> fields = new ArrayList<>(); 084 parser.parse(pattern, converters, fields, false, false, false); 085 final FormattingInfo[] infoArray = new FormattingInfo[fields.size()]; 086 patternFields = fields.toArray(infoArray); 087 final ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()]; 088 patternConverters = converters.toArray(converterArray); 089 090 for (final ArrayPatternConverter converter : patternConverters) { 091 if (converter instanceof DatePatternConverter) { 092 final DatePatternConverter dateConverter = (DatePatternConverter) converter; 093 frequency = calculateFrequency(dateConverter.getPattern()); 094 } 095 } 096 } 097 098 /** 099 * Copy constructor with another pattern as source. 100 * 101 * @param pattern The file pattern. 102 * @param copy Source pattern processor 103 */ 104 public PatternProcessor(final String pattern, final PatternProcessor copy) { 105 this(pattern); 106 this.prevFileTime = copy.prevFileTime; 107 this.nextFileTime = copy.nextFileTime; 108 this.currentFileTime = copy.currentFileTime; 109 } 110 111 public void setTimeBased(boolean isTimeBased) { 112 this.isTimeBased = isTimeBased; 113 } 114 115 public long getCurrentFileTime() { 116 return currentFileTime; 117 } 118 119 public void setCurrentFileTime(final long currentFileTime) { 120 this.currentFileTime = currentFileTime; 121 } 122 123 public long getPrevFileTime() { 124 return prevFileTime; 125 } 126 127 public void setPrevFileTime(final long prevFileTime) { 128 LOGGER.debug("Setting prev file time to {}", new Date(prevFileTime)); 129 this.prevFileTime = prevFileTime; 130 } 131 132 /** 133 * Returns the next potential rollover time. 134 * @param currentMillis The current time. 135 * @param increment The increment to the next time. 136 * @param modulus If true the time will be rounded to occur on a boundary aligned with the increment. 137 * @return the next potential rollover time and the timestamp for the target file. 138 */ 139 public long getNextTime(final long currentMillis, final int increment, final boolean modulus) { 140 // 141 // https://issues.apache.org/jira/browse/LOG4J2-1232 142 // Call setMinimalDaysInFirstWeek(7); 143 // 144 prevFileTime = nextFileTime; 145 long nextTime; 146 147 if (frequency == null) { 148 throw new IllegalStateException("Pattern does not contain a date"); 149 } 150 final Calendar currentCal = Calendar.getInstance(); 151 currentCal.setTimeInMillis(currentMillis); 152 final Calendar cal = Calendar.getInstance(); 153 currentCal.setMinimalDaysInFirstWeek(7); 154 cal.setMinimalDaysInFirstWeek(7); 155 cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0); 156 cal.set(Calendar.MILLISECOND, 0); 157 if (frequency == RolloverFrequency.ANNUALLY) { 158 increment(cal, Calendar.YEAR, increment, modulus); 159 nextTime = cal.getTimeInMillis(); 160 cal.add(Calendar.YEAR, -1); 161 nextFileTime = cal.getTimeInMillis(); 162 return debugGetNextTime(nextTime); 163 } 164 cal.set(Calendar.MONTH, currentCal.get(Calendar.MONTH)); 165 if (frequency == RolloverFrequency.MONTHLY) { 166 increment(cal, Calendar.MONTH, increment, modulus); 167 nextTime = cal.getTimeInMillis(); 168 cal.add(Calendar.MONTH, -1); 169 nextFileTime = cal.getTimeInMillis(); 170 return debugGetNextTime(nextTime); 171 } 172 if (frequency == RolloverFrequency.WEEKLY) { 173 cal.set(Calendar.WEEK_OF_YEAR, currentCal.get(Calendar.WEEK_OF_YEAR)); 174 increment(cal, Calendar.WEEK_OF_YEAR, increment, modulus); 175 cal.set(Calendar.DAY_OF_WEEK, currentCal.getFirstDayOfWeek()); 176 nextTime = cal.getTimeInMillis(); 177 cal.add(Calendar.WEEK_OF_YEAR, -1); 178 nextFileTime = cal.getTimeInMillis(); 179 return debugGetNextTime(nextTime); 180 } 181 cal.set(Calendar.DAY_OF_YEAR, currentCal.get(Calendar.DAY_OF_YEAR)); 182 if (frequency == RolloverFrequency.DAILY) { 183 increment(cal, Calendar.DAY_OF_YEAR, increment, modulus); 184 nextTime = cal.getTimeInMillis(); 185 cal.add(Calendar.DAY_OF_YEAR, -1); 186 nextFileTime = cal.getTimeInMillis(); 187 return debugGetNextTime(nextTime); 188 } 189 cal.set(Calendar.HOUR_OF_DAY, currentCal.get(Calendar.HOUR_OF_DAY)); 190 if (frequency == RolloverFrequency.HOURLY) { 191 increment(cal, Calendar.HOUR_OF_DAY, increment, modulus); 192 nextTime = cal.getTimeInMillis(); 193 cal.add(Calendar.HOUR_OF_DAY, -1); 194 nextFileTime = cal.getTimeInMillis(); 195 return debugGetNextTime(nextTime); 196 } 197 cal.set(Calendar.MINUTE, currentCal.get(Calendar.MINUTE)); 198 if (frequency == RolloverFrequency.EVERY_MINUTE) { 199 increment(cal, Calendar.MINUTE, increment, modulus); 200 nextTime = cal.getTimeInMillis(); 201 cal.add(Calendar.MINUTE, -1); 202 nextFileTime = cal.getTimeInMillis(); 203 return debugGetNextTime(nextTime); 204 } 205 cal.set(Calendar.SECOND, currentCal.get(Calendar.SECOND)); 206 if (frequency == RolloverFrequency.EVERY_SECOND) { 207 increment(cal, Calendar.SECOND, increment, modulus); 208 nextTime = cal.getTimeInMillis(); 209 cal.add(Calendar.SECOND, -1); 210 nextFileTime = cal.getTimeInMillis(); 211 return debugGetNextTime(nextTime); 212 } 213 cal.set(Calendar.MILLISECOND, currentCal.get(Calendar.MILLISECOND)); 214 increment(cal, Calendar.MILLISECOND, increment, modulus); 215 nextTime = cal.getTimeInMillis(); 216 cal.add(Calendar.MILLISECOND, -1); 217 nextFileTime = cal.getTimeInMillis(); 218 return debugGetNextTime(nextTime); 219 } 220 221 public void updateTime() { 222 if (nextFileTime != 0 || !isTimeBased) { 223 prevFileTime = nextFileTime; 224 } 225 } 226 227 private long debugGetNextTime(final long nextTime) { 228 if (LOGGER.isTraceEnabled()) { 229 LOGGER.trace("PatternProcessor.getNextTime returning {}, nextFileTime={}, prevFileTime={}, current={}, freq={}", // 230 format(nextTime), format(nextFileTime), format(prevFileTime), format(System.currentTimeMillis()), frequency); 231 } 232 return nextTime; 233 } 234 235 private String format(final long time) { 236 return new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss.SSS").format(new Date(time)); 237 } 238 239 private void increment(final Calendar cal, final int type, final int increment, final boolean modulate) { 240 final int interval = modulate ? increment - (cal.get(type) % increment) : increment; 241 cal.add(type, interval); 242 } 243 244 /** 245 * Format file name. 246 * @param buf string buffer to which formatted file name is appended, may not be null. 247 * @param obj object to be evaluated in formatting, may not be null. 248 */ 249 public final void formatFileName(final StringBuilder buf, final boolean useCurrentTime, final Object obj) { 250 long time = useCurrentTime ? currentFileTime : prevFileTime; 251 if (time == 0) { 252 time = System.currentTimeMillis(); 253 } 254 formatFileName(buf, new Date(time), obj); 255 } 256 257 /** 258 * Formats file name. 259 * @param subst The StrSubstitutor. 260 * @param buf string buffer to which formatted file name is appended, may not be null. 261 * @param obj object to be evaluated in formatting, may not be null. 262 */ 263 public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final Object obj) { 264 formatFileName(subst, buf, false, obj); 265 } 266 267 /** 268 * Formats file name. 269 * @param subst The StrSubstitutor. 270 * @param buf string buffer to which formatted file name is appended, may not be null. 271 * @param obj object to be evaluated in formatting, may not be null. 272 */ 273 public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final boolean useCurrentTime, 274 final Object obj) { 275 // LOG4J2-628: we deliberately use System time, not the log4j.Clock time 276 // for creating the file name of rolled-over files. 277 LOGGER.debug("Formatting file name. useCurrentTime={}. currentFileTime={}, prevFileTime={}", 278 useCurrentTime, currentFileTime, prevFileTime); 279 final long time = useCurrentTime ? currentFileTime != 0 ? currentFileTime : System.currentTimeMillis() : 280 prevFileTime != 0 ? prevFileTime : System.currentTimeMillis(); 281 formatFileName(buf, new Date(time), obj); 282 final LogEvent event = new Log4jLogEvent.Builder().setTimeMillis(time).build(); 283 final String fileName = subst.replace(event, buf); 284 buf.setLength(0); 285 buf.append(fileName); 286 } 287 288 /** 289 * Formats file name. 290 * @param buf string buffer to which formatted file name is appended, may not be null. 291 * @param objects objects to be evaluated in formatting, may not be null. 292 */ 293 protected final void formatFileName(final StringBuilder buf, final Object... objects) { 294 for (int i = 0; i < patternConverters.length; i++) { 295 final int fieldStart = buf.length(); 296 patternConverters[i].format(buf, objects); 297 298 if (patternFields[i] != null) { 299 patternFields[i].format(fieldStart, buf); 300 } 301 } 302 } 303 304 private RolloverFrequency calculateFrequency(final String pattern) { 305 if (patternContains(pattern, MILLIS_CHAR)) { 306 return RolloverFrequency.EVERY_MILLISECOND; 307 } 308 if (patternContains(pattern, SECOND_CHAR)) { 309 return RolloverFrequency.EVERY_SECOND; 310 } 311 if (patternContains(pattern, MINUTE_CHAR)) { 312 return RolloverFrequency.EVERY_MINUTE; 313 } 314 if (patternContains(pattern, HOUR_CHARS)) { 315 return RolloverFrequency.HOURLY; 316 } 317 if (patternContains(pattern, DAY_CHARS)) { 318 return RolloverFrequency.DAILY; 319 } 320 if (patternContains(pattern, WEEK_CHARS)) { 321 return RolloverFrequency.WEEKLY; 322 } 323 if (patternContains(pattern, MONTH_CHAR)) { 324 return RolloverFrequency.MONTHLY; 325 } 326 if (patternContains(pattern, YEAR_CHAR)) { 327 return RolloverFrequency.ANNUALLY; 328 } 329 return null; 330 } 331 332 private PatternParser createPatternParser() { 333 334 return new PatternParser(null, KEY, null); 335 } 336 337 private boolean patternContains(final String pattern, final char... chars) { 338 for (final char character : chars) { 339 if (patternContains(pattern, character)) { 340 return true; 341 } 342 } 343 return false; 344 } 345 346 private boolean patternContains(final String pattern, final char character) { 347 return pattern.indexOf(character) >= 0; 348 } 349 350 public RolloverFrequency getFrequency() { 351 return frequency; 352 } 353 354 public long getNextFileTime() { 355 return nextFileTime; 356 } 357 358}