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 * Parse 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 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}