    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    }