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